@sentry/junior 0.53.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 +1934 -1658
- 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(),
|
|
@@ -1479,6 +1490,9 @@ var StateBackedMcpOAuthClientProvider = class {
|
|
|
1479
1490
|
this.sessionContext = sessionContext;
|
|
1480
1491
|
this.clientMetadata = createClientMetadata(callbackUrl);
|
|
1481
1492
|
}
|
|
1493
|
+
authSessionId;
|
|
1494
|
+
callbackUrl;
|
|
1495
|
+
sessionContext;
|
|
1482
1496
|
clientMetadata;
|
|
1483
1497
|
get redirectUrl() {
|
|
1484
1498
|
return this.callbackUrl;
|
|
@@ -2128,39 +2142,6 @@ function markTurnFailed(args) {
|
|
|
2128
2142
|
args.updateConversationStats(args.conversation);
|
|
2129
2143
|
}
|
|
2130
2144
|
|
|
2131
|
-
// src/chat/runtime/turn-user-message.ts
|
|
2132
|
-
function normalizeSlackMessageTs(value) {
|
|
2133
|
-
const trimmed = value?.trim();
|
|
2134
|
-
return trimmed && /^\d+(?:\.\d+)?$/.test(trimmed) ? trimmed : void 0;
|
|
2135
|
-
}
|
|
2136
|
-
function getTurnUserMessage(conversation, sessionId) {
|
|
2137
|
-
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
2138
|
-
const message = conversation.messages[index];
|
|
2139
|
-
if (message?.role !== "user") {
|
|
2140
|
-
continue;
|
|
2141
|
-
}
|
|
2142
|
-
if (buildDeterministicTurnId(message.id) === sessionId) {
|
|
2143
|
-
return message;
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
return void 0;
|
|
2147
|
-
}
|
|
2148
|
-
function getTurnUserMessageId(conversation, sessionId) {
|
|
2149
|
-
return getTurnUserMessage(conversation, sessionId)?.id;
|
|
2150
|
-
}
|
|
2151
|
-
function getTurnUserSlackMessageTs(message) {
|
|
2152
|
-
return normalizeSlackMessageTs(message?.meta?.slackTs) ?? normalizeSlackMessageTs(message?.id);
|
|
2153
|
-
}
|
|
2154
|
-
function getTurnUserReplyAttachmentContext(message) {
|
|
2155
|
-
const inboundAttachmentCount = message?.meta?.attachmentCount ?? 0;
|
|
2156
|
-
const imageAttachmentCount = message?.meta?.imageAttachmentCount ?? 0;
|
|
2157
|
-
const imagesHydrated = message?.meta?.imagesHydrated === true;
|
|
2158
|
-
return {
|
|
2159
|
-
...inboundAttachmentCount > 0 ? { inboundAttachmentCount } : {},
|
|
2160
|
-
...!imagesHydrated && imageAttachmentCount > 0 ? { omittedImageAttachmentCount: imageAttachmentCount } : {}
|
|
2161
|
-
};
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
2145
|
// src/chat/services/conversation-memory.ts
|
|
2165
2146
|
var CONTEXT_COMPACTION_TRIGGER_TOKENS = 9e3;
|
|
2166
2147
|
var CONTEXT_COMPACTION_TARGET_TOKENS = 7e3;
|
|
@@ -2451,589 +2432,1015 @@ function getConversationMessageSlackTs(message) {
|
|
|
2451
2432
|
return message.meta?.slackTs ?? toOptionalString(message.id);
|
|
2452
2433
|
}
|
|
2453
2434
|
|
|
2454
|
-
// src/chat/
|
|
2455
|
-
import {
|
|
2456
|
-
|
|
2457
|
-
// src/chat/prompt.ts
|
|
2458
|
-
import fs from "fs";
|
|
2459
|
-
import path2 from "path";
|
|
2460
|
-
|
|
2461
|
-
// src/chat/turn-context-tag.ts
|
|
2462
|
-
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
2435
|
+
// src/chat/state/turn-session-store.ts
|
|
2436
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
|
|
2463
2437
|
|
|
2464
|
-
// src/chat/
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
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}`;
|
|
2468
2443
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
2472
|
-
function truncateStatusText(text) {
|
|
2473
|
-
const trimmed = text.trim();
|
|
2474
|
-
if (!trimmed) {
|
|
2475
|
-
return "";
|
|
2476
|
-
}
|
|
2477
|
-
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
2478
|
-
return trimmed;
|
|
2479
|
-
}
|
|
2480
|
-
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
2444
|
+
function parsePiMessage(value) {
|
|
2445
|
+
return isRecord(value) ? value : void 0;
|
|
2481
2446
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2491
|
-
const line = lines[i];
|
|
2492
|
-
const isCodeFence = codeBlockPattern.test(line.trimStart());
|
|
2493
|
-
if (isCodeFence) {
|
|
2494
|
-
if (!inCodeBlock) {
|
|
2495
|
-
const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
|
|
2496
|
-
if (prev2 !== void 0 && prev2.trim() !== "") {
|
|
2497
|
-
result.push("");
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
inCodeBlock = !inCodeBlock;
|
|
2501
|
-
result.push(line);
|
|
2502
|
-
continue;
|
|
2503
|
-
}
|
|
2504
|
-
if (inCodeBlock) {
|
|
2505
|
-
result.push(line);
|
|
2506
|
-
continue;
|
|
2507
|
-
}
|
|
2508
|
-
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
2509
|
-
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
2510
|
-
result.push("");
|
|
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;
|
|
2511
2455
|
}
|
|
2512
|
-
result.push(line);
|
|
2513
2456
|
}
|
|
2514
|
-
return
|
|
2457
|
+
return limit;
|
|
2515
2458
|
}
|
|
2516
|
-
function
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
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
|
+
}
|
|
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);
|
|
2479
|
+
}
|
|
2480
|
+
return messages.length === messageCount ? messages : void 0;
|
|
2520
2481
|
}
|
|
2521
|
-
function
|
|
2522
|
-
const
|
|
2523
|
-
if (
|
|
2524
|
-
return
|
|
2482
|
+
async function loadExistingPiSessionMessages(scope, maxCount) {
|
|
2483
|
+
const count = normalizeMessageCount(maxCount);
|
|
2484
|
+
if (count === 0) {
|
|
2485
|
+
return [];
|
|
2525
2486
|
}
|
|
2526
|
-
|
|
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
|
+
}
|
|
2501
|
+
messages.push(message);
|
|
2502
|
+
}
|
|
2503
|
+
return messages;
|
|
2504
|
+
}
|
|
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
|
+
);
|
|
2527
2522
|
}
|
|
2528
2523
|
|
|
2529
|
-
// src/chat/
|
|
2530
|
-
var
|
|
2531
|
-
var
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
if (!text) {
|
|
2535
|
-
return 0;
|
|
2536
|
-
}
|
|
2537
|
-
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}`;
|
|
2538
2529
|
}
|
|
2539
|
-
function
|
|
2540
|
-
return
|
|
2530
|
+
function toFiniteNonNegativeNumber(value) {
|
|
2531
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
2541
2532
|
}
|
|
2542
|
-
function
|
|
2543
|
-
if (
|
|
2544
|
-
return
|
|
2545
|
-
}
|
|
2546
|
-
const bounded = text.slice(0, maxChars);
|
|
2547
|
-
const newlineIndex = bounded.lastIndexOf("\n");
|
|
2548
|
-
if (newlineIndex > 0) {
|
|
2549
|
-
return newlineIndex;
|
|
2533
|
+
function parseAgentTurnUsage(value) {
|
|
2534
|
+
if (!isRecord(value)) {
|
|
2535
|
+
return void 0;
|
|
2550
2536
|
}
|
|
2551
|
-
const
|
|
2552
|
-
|
|
2553
|
-
|
|
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
|
+
}
|
|
2554
2549
|
}
|
|
2555
|
-
return
|
|
2550
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
2556
2551
|
}
|
|
2557
|
-
function
|
|
2558
|
-
if (
|
|
2559
|
-
return
|
|
2552
|
+
function parseStoredRecord(value) {
|
|
2553
|
+
if (isRecord(value)) {
|
|
2554
|
+
return value;
|
|
2560
2555
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
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;
|
|
2564
2564
|
}
|
|
2565
|
-
return lines.slice(0, maxLines).join("\n");
|
|
2566
|
-
}
|
|
2567
|
-
function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
2568
|
-
return {
|
|
2569
|
-
maxChars: Math.max(1, maxChars - suffix.length),
|
|
2570
|
-
maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
|
|
2571
|
-
};
|
|
2572
2565
|
}
|
|
2573
|
-
function
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
|
|
2578
|
-
};
|
|
2579
|
-
}
|
|
2580
|
-
function getFenceContinuation(text) {
|
|
2581
|
-
let open;
|
|
2582
|
-
for (const line of text.split("\n")) {
|
|
2583
|
-
const trimmed = line.trimStart();
|
|
2584
|
-
const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
2585
|
-
if (!openerMatch) {
|
|
2586
|
-
continue;
|
|
2587
|
-
}
|
|
2588
|
-
if (!open) {
|
|
2589
|
-
open = {
|
|
2590
|
-
fence: openerMatch[1],
|
|
2591
|
-
openerLine: trimmed
|
|
2592
|
-
};
|
|
2593
|
-
continue;
|
|
2594
|
-
}
|
|
2595
|
-
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
|
|
2596
|
-
open = void 0;
|
|
2597
|
-
}
|
|
2566
|
+
function parseAgentTurnSessionRecord(value) {
|
|
2567
|
+
const parsed = parseStoredRecord(value);
|
|
2568
|
+
if (!parsed) {
|
|
2569
|
+
return void 0;
|
|
2598
2570
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
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;
|
|
2601
2586
|
}
|
|
2587
|
+
const legacyPiMessages = Array.isArray(parsed.piMessages) ? parsed.piMessages : [];
|
|
2588
|
+
const messageCount = toFiniteNonNegativeNumber(parsed.messageCount) ?? legacyPiMessages.length;
|
|
2602
2589
|
return {
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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
|
+
}
|
|
2607
2610
|
};
|
|
2608
2611
|
}
|
|
2609
|
-
function
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
}
|
|
2613
|
-
function stripTrailingContinuationMarker(text) {
|
|
2614
|
-
return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
|
|
2615
|
-
}
|
|
2616
|
-
function takeSlackContinuationChunk(text, budget) {
|
|
2617
|
-
let { prefix, rest } = takeSlackInlinePrefix(text, budget);
|
|
2618
|
-
if (!rest) {
|
|
2619
|
-
({ prefix, rest } = takeSlackInlinePrefix(
|
|
2620
|
-
text,
|
|
2621
|
-
forceSplitBudget(text, budget)
|
|
2622
|
-
));
|
|
2612
|
+
function materializePiMessages(legacyPiMessages, messageCount, sessionMessages) {
|
|
2613
|
+
if (messageCount === 0) {
|
|
2614
|
+
return [];
|
|
2623
2615
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
return { prefix, rest };
|
|
2616
|
+
if (sessionMessages) {
|
|
2617
|
+
return sessionMessages;
|
|
2627
2618
|
}
|
|
2628
|
-
|
|
2629
|
-
|
|
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)
|
|
2630
2629
|
);
|
|
2631
|
-
|
|
2632
|
-
if (!
|
|
2633
|
-
|
|
2634
|
-
text,
|
|
2635
|
-
forceSplitBudget(text, carryoverBudget)
|
|
2636
|
-
));
|
|
2630
|
+
const parsed = parseAgentTurnSessionRecord(value);
|
|
2631
|
+
if (!parsed) {
|
|
2632
|
+
return void 0;
|
|
2637
2633
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
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;
|
|
2641
2646
|
}
|
|
2642
2647
|
return {
|
|
2643
|
-
|
|
2644
|
-
|
|
2648
|
+
...parsed.record,
|
|
2649
|
+
piMessages
|
|
2645
2650
|
};
|
|
2646
2651
|
}
|
|
2647
|
-
function
|
|
2648
|
-
const
|
|
2649
|
-
|
|
2650
|
-
|
|
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 } : {}
|
|
2651
2690
|
};
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
|
|
2658
|
-
})();
|
|
2691
|
+
await stateAdapter.set(
|
|
2692
|
+
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
2693
|
+
checkpoint,
|
|
2694
|
+
ttlMs
|
|
2695
|
+
);
|
|
2659
2696
|
return {
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
rest
|
|
2697
|
+
...checkpoint,
|
|
2698
|
+
piMessages: [...args.piMessages]
|
|
2663
2699
|
};
|
|
2664
2700
|
}
|
|
2665
|
-
function
|
|
2666
|
-
const
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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;
|
|
2671
2708
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
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;
|
|
2674
2730
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
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;
|
|
2683
2752
|
}
|
|
2684
|
-
|
|
2685
|
-
return {
|
|
2686
|
-
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
2687
|
-
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
2688
|
-
};
|
|
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());
|
|
2689
2754
|
}
|
|
2690
|
-
function
|
|
2691
|
-
const
|
|
2692
|
-
if (!
|
|
2693
|
-
return
|
|
2755
|
+
function getConversationPendingAuth(args) {
|
|
2756
|
+
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
2757
|
+
if (!pendingAuth) {
|
|
2758
|
+
return void 0;
|
|
2694
2759
|
}
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
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."
|
|
2709
2782
|
});
|
|
2710
|
-
chunks.push(renderedPrefix);
|
|
2711
|
-
remaining = rest;
|
|
2712
2783
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
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;
|
|
2715
2792
|
}
|
|
2716
|
-
return
|
|
2793
|
+
return false;
|
|
2717
2794
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
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
|
+
};
|
|
2720
2834
|
}
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
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;
|
|
2730
2849
|
}
|
|
2731
|
-
throw new Error(
|
|
2732
|
-
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
2733
|
-
);
|
|
2734
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;
|
|
2735
2863
|
return {
|
|
2736
|
-
|
|
2737
|
-
|
|
2864
|
+
...inboundAttachmentCount > 0 ? { inboundAttachmentCount } : {},
|
|
2865
|
+
...!imagesHydrated && imageAttachmentCount > 0 ? { omittedImageAttachmentCount: imageAttachmentCount } : {}
|
|
2738
2866
|
};
|
|
2739
2867
|
}
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
};
|
|
2868
|
+
|
|
2869
|
+
// src/chat/respond.ts
|
|
2870
|
+
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
2744
2871
|
|
|
2745
2872
|
// src/chat/prompt.ts
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
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;
|
|
2751
2883
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
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("");
|
|
2769
2913
|
}
|
|
2770
|
-
return raw;
|
|
2771
2914
|
}
|
|
2772
|
-
|
|
2915
|
+
inCodeBlock = !inCodeBlock;
|
|
2916
|
+
result.push(line);
|
|
2917
|
+
continue;
|
|
2918
|
+
}
|
|
2919
|
+
if (inCodeBlock) {
|
|
2920
|
+
result.push(line);
|
|
2773
2921
|
continue;
|
|
2774
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);
|
|
2775
2928
|
}
|
|
2776
|
-
return
|
|
2929
|
+
return result.join("\n");
|
|
2777
2930
|
}
|
|
2778
|
-
function
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
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 "";
|
|
2782
2940
|
}
|
|
2783
|
-
|
|
2784
|
-
"soul_load_fallback",
|
|
2785
|
-
{},
|
|
2786
|
-
{
|
|
2787
|
-
"app.file.candidates": soulPathCandidates()
|
|
2788
|
-
},
|
|
2789
|
-
"SOUL.md not found; using built-in default personality"
|
|
2790
|
-
);
|
|
2791
|
-
return DEFAULT_SOUL;
|
|
2941
|
+
return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
|
|
2792
2942
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
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;
|
|
2795
2953
|
}
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
{},
|
|
2803
|
-
{
|
|
2804
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
2805
|
-
},
|
|
2806
|
-
"Failed to load SOUL.md; using built-in default personality"
|
|
2807
|
-
);
|
|
2808
|
-
return DEFAULT_SOUL;
|
|
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;
|
|
2809
2960
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
return
|
|
2814
|
-
} catch (error) {
|
|
2815
|
-
logWarn(
|
|
2816
|
-
"world_load_failed",
|
|
2817
|
-
{},
|
|
2818
|
-
{
|
|
2819
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
2820
|
-
},
|
|
2821
|
-
"Failed to load WORLD.md; omitting world prompt context"
|
|
2822
|
-
);
|
|
2823
|
-
return null;
|
|
2961
|
+
const bounded = text.slice(0, maxChars);
|
|
2962
|
+
const newlineIndex = bounded.lastIndexOf("\n");
|
|
2963
|
+
if (newlineIndex > 0) {
|
|
2964
|
+
return newlineIndex;
|
|
2824
2965
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2966
|
+
const spaceIndex = bounded.lastIndexOf(" ");
|
|
2967
|
+
if (spaceIndex > 0) {
|
|
2968
|
+
return spaceIndex;
|
|
2969
|
+
}
|
|
2970
|
+
return maxChars;
|
|
2828
2971
|
}
|
|
2829
|
-
function
|
|
2830
|
-
if (
|
|
2831
|
-
return
|
|
2972
|
+
function splitByLineBudget(text, maxLines) {
|
|
2973
|
+
if (maxLines <= 0) {
|
|
2974
|
+
return "";
|
|
2832
2975
|
}
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
return escapeXml(String(value));
|
|
2976
|
+
const lines = text.split("\n");
|
|
2977
|
+
if (lines.length <= maxLines) {
|
|
2978
|
+
return text;
|
|
2837
2979
|
}
|
|
2980
|
+
return lines.slice(0, maxLines).join("\n");
|
|
2838
2981
|
}
|
|
2839
|
-
function
|
|
2840
|
-
|
|
2841
|
-
|
|
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
|
|
3007
|
+
};
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
|
|
3011
|
+
open = void 0;
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
if (!open) {
|
|
2842
3015
|
return null;
|
|
2843
3016
|
}
|
|
2844
|
-
return
|
|
3017
|
+
return {
|
|
3018
|
+
closeSuffix: text.endsWith("\n") ? open.fence : `
|
|
3019
|
+
${open.fence}`,
|
|
3020
|
+
reopenPrefix: `${open.openerLine}
|
|
3021
|
+
`
|
|
3022
|
+
};
|
|
2845
3023
|
}
|
|
2846
|
-
function
|
|
2847
|
-
|
|
3024
|
+
function appendSlackSuffix(text, marker) {
|
|
3025
|
+
const carryover = getFenceContinuation(text);
|
|
3026
|
+
return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
|
|
2848
3027
|
}
|
|
2849
|
-
function
|
|
2850
|
-
return
|
|
3028
|
+
function stripTrailingContinuationMarker(text) {
|
|
3029
|
+
return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
|
|
2851
3030
|
}
|
|
2852
|
-
function
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
if (skill.pluginProvider) {
|
|
2860
|
-
lines.push(` <provider>${escapeXml(skill.pluginProvider)}</provider>`);
|
|
3031
|
+
function takeSlackContinuationChunk(text, budget) {
|
|
3032
|
+
let { prefix, rest } = takeSlackInlinePrefix(text, budget);
|
|
3033
|
+
if (!rest) {
|
|
3034
|
+
({ prefix, rest } = takeSlackInlinePrefix(
|
|
3035
|
+
text,
|
|
3036
|
+
forceSplitBudget(text, budget)
|
|
3037
|
+
));
|
|
2861
3038
|
}
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
const
|
|
2867
|
-
|
|
3039
|
+
let carryover = rest ? getFenceContinuation(prefix) : null;
|
|
3040
|
+
if (!carryover) {
|
|
3041
|
+
return { prefix, rest };
|
|
3042
|
+
}
|
|
3043
|
+
const carryoverBudget = reserveInlineBudgetForSuffix(
|
|
3044
|
+
`${carryover.closeSuffix}${CONTINUED_MARKER}`
|
|
2868
3045
|
);
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
...autoSelectable.length > 0 ? [
|
|
2876
|
-
"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."
|
|
2877
|
-
] : []
|
|
2878
|
-
];
|
|
2879
|
-
for (const skill of autoSelectable) {
|
|
2880
|
-
available.push(...formatSkillEntry(skill));
|
|
3046
|
+
({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
|
|
3047
|
+
if (!rest) {
|
|
3048
|
+
({ prefix, rest } = takeSlackInlinePrefix(
|
|
3049
|
+
text,
|
|
3050
|
+
forceSplitBudget(text, carryoverBudget)
|
|
3051
|
+
));
|
|
2881
3052
|
}
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
const userCallable = [
|
|
2886
|
-
"<user-callable-skills>",
|
|
2887
|
-
"The user's current message explicitly references this skill by name. Load it when relevant to the request."
|
|
2888
|
-
];
|
|
2889
|
-
for (const skill of invokedExplicitOnly) {
|
|
2890
|
-
userCallable.push(...formatSkillEntry(skill));
|
|
2891
|
-
}
|
|
2892
|
-
userCallable.push("</user-callable-skills>");
|
|
2893
|
-
sections.push(userCallable.join("\n"));
|
|
3053
|
+
carryover = rest ? getFenceContinuation(prefix) : null;
|
|
3054
|
+
if (!carryover) {
|
|
3055
|
+
return { prefix, rest };
|
|
2894
3056
|
}
|
|
2895
|
-
return
|
|
3057
|
+
return {
|
|
3058
|
+
prefix,
|
|
3059
|
+
rest: `${carryover.reopenPrefix}${rest}`
|
|
3060
|
+
};
|
|
2896
3061
|
}
|
|
2897
|
-
function
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
);
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
}
|
|
2914
|
-
lines.push("</loaded-skills>");
|
|
2915
|
-
return lines.join("\n");
|
|
3062
|
+
function takeSlackContinuationPrefix(text, options) {
|
|
3063
|
+
const budget = {
|
|
3064
|
+
maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
|
|
3065
|
+
maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
|
|
3066
|
+
};
|
|
3067
|
+
const { prefix, rest } = (() => {
|
|
3068
|
+
if (options?.forceSplit) {
|
|
3069
|
+
return takeSlackContinuationChunk(text, budget);
|
|
3070
|
+
}
|
|
3071
|
+
const initial = takeSlackInlinePrefix(text, budget);
|
|
3072
|
+
return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
|
|
3073
|
+
})();
|
|
3074
|
+
return {
|
|
3075
|
+
prefix,
|
|
3076
|
+
renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
|
|
3077
|
+
rest
|
|
3078
|
+
};
|
|
2916
3079
|
}
|
|
2917
|
-
function
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
"Config keys and default targets per provider; use after a skill is loaded. Run authenticated provider commands directly after resolving target defaults; let the runtime handle auth pauses/resumes."
|
|
2924
|
-
];
|
|
2925
|
-
for (const provider of providers) {
|
|
2926
|
-
lines.push(`- provider: ${escapeXml(provider.name)}`);
|
|
2927
|
-
lines.push(
|
|
2928
|
-
` - config_keys: ${provider.configKeys.length > 0 ? escapeXml(provider.configKeys.join(", ")) : "none"}`
|
|
2929
|
-
);
|
|
2930
|
-
lines.push(
|
|
2931
|
-
` - default_context: ${provider.target ? escapeXml(
|
|
2932
|
-
`${provider.target.type} via ${provider.target.configKey}`
|
|
2933
|
-
) : "none"}`
|
|
2934
|
-
);
|
|
3080
|
+
function takeSlackInlinePrefix(text, options) {
|
|
3081
|
+
const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
|
|
3082
|
+
const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
|
|
3083
|
+
const normalized = text.replace(/\r\n?/g, "\n");
|
|
3084
|
+
if (!normalized) {
|
|
3085
|
+
return { prefix: "", rest: "" };
|
|
2935
3086
|
}
|
|
2936
|
-
|
|
2937
|
-
}
|
|
2938
|
-
function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
2939
|
-
if (catalogs.length === 0) {
|
|
2940
|
-
return null;
|
|
3087
|
+
if (fitsInlineBudget(normalized, maxChars, maxLines)) {
|
|
3088
|
+
return { prefix: normalized, rest: "" };
|
|
2941
3089
|
}
|
|
2942
|
-
const
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
);
|
|
2951
|
-
lines.push(" </catalog>");
|
|
3090
|
+
const lineBounded = splitByLineBudget(normalized, maxLines);
|
|
3091
|
+
const cutIndex = findSplitIndex(lineBounded, maxChars);
|
|
3092
|
+
const prefix = lineBounded.slice(0, cutIndex).trimEnd();
|
|
3093
|
+
if (prefix) {
|
|
3094
|
+
return {
|
|
3095
|
+
prefix,
|
|
3096
|
+
rest: normalized.slice(prefix.length).trimStart()
|
|
3097
|
+
};
|
|
2952
3098
|
}
|
|
2953
|
-
|
|
3099
|
+
const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
|
|
3100
|
+
return {
|
|
3101
|
+
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
3102
|
+
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
3103
|
+
};
|
|
2954
3104
|
}
|
|
2955
|
-
function
|
|
2956
|
-
const
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
if (guidedTools.length === 0) {
|
|
2960
|
-
return null;
|
|
3105
|
+
function splitSlackReplyText(text, options) {
|
|
3106
|
+
const normalized = renderSlackMrkdwn(text);
|
|
3107
|
+
if (!normalized) {
|
|
3108
|
+
return [];
|
|
2961
3109
|
}
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
3110
|
+
const chunks = [];
|
|
3111
|
+
const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
3112
|
+
let remaining = normalized;
|
|
3113
|
+
while (remaining) {
|
|
3114
|
+
const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
|
|
3115
|
+
if (fitsFinalChunk) {
|
|
3116
|
+
chunks.push(
|
|
3117
|
+
options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
|
|
3118
|
+
);
|
|
3119
|
+
break;
|
|
2967
3120
|
}
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
3121
|
+
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
3122
|
+
...continuationBudget,
|
|
3123
|
+
forceSplit: true
|
|
3124
|
+
});
|
|
3125
|
+
chunks.push(renderedPrefix);
|
|
3126
|
+
remaining = rest;
|
|
3127
|
+
}
|
|
3128
|
+
if (chunks.length === 2) {
|
|
3129
|
+
chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
|
|
3130
|
+
}
|
|
3131
|
+
return chunks;
|
|
3132
|
+
}
|
|
3133
|
+
function getSlackContinuationBudget() {
|
|
3134
|
+
return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
3135
|
+
}
|
|
3136
|
+
function buildSlackOutputMessage(text, files) {
|
|
3137
|
+
const normalized = renderSlackMrkdwn(text);
|
|
3138
|
+
const fileCount = files?.length ?? 0;
|
|
3139
|
+
if (!normalized) {
|
|
3140
|
+
if (fileCount > 0) {
|
|
3141
|
+
return {
|
|
3142
|
+
raw: "",
|
|
3143
|
+
files
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
throw new Error(
|
|
3147
|
+
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
3148
|
+
);
|
|
3149
|
+
}
|
|
3150
|
+
return {
|
|
3151
|
+
markdown: normalized,
|
|
3152
|
+
files
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
var slackOutputPolicy = {
|
|
3156
|
+
maxInlineChars: MAX_INLINE_CHARS,
|
|
3157
|
+
maxInlineLines: MAX_INLINE_LINES
|
|
3158
|
+
};
|
|
3159
|
+
|
|
3160
|
+
// src/chat/prompt.ts
|
|
3161
|
+
var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
|
|
3162
|
+
function getLoggedMarkdownFiles() {
|
|
3163
|
+
const globalState = globalThis;
|
|
3164
|
+
globalState.__juniorLoggedMarkdownFiles ??= /* @__PURE__ */ new Set();
|
|
3165
|
+
return globalState.__juniorLoggedMarkdownFiles;
|
|
3166
|
+
}
|
|
3167
|
+
function loadOptionalMarkdownFile(candidates, fileName) {
|
|
3168
|
+
for (const resolved of candidates) {
|
|
3169
|
+
try {
|
|
3170
|
+
const raw = fs.readFileSync(resolved, "utf8").trim();
|
|
3171
|
+
if (raw.length > 0) {
|
|
3172
|
+
const loggedMarkdownFiles = getLoggedMarkdownFiles();
|
|
3173
|
+
const logKey = `${fileName}:${resolved}`;
|
|
3174
|
+
if (!loggedMarkdownFiles.has(logKey)) {
|
|
3175
|
+
loggedMarkdownFiles.add(logKey);
|
|
3176
|
+
logInfo(
|
|
3177
|
+
`${fileName.toLowerCase()}_loaded`,
|
|
3178
|
+
{},
|
|
3179
|
+
{
|
|
3180
|
+
"file.path": resolved
|
|
3181
|
+
},
|
|
3182
|
+
`Loaded ${fileName}`
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
return raw;
|
|
2971
3186
|
}
|
|
3187
|
+
} catch {
|
|
3188
|
+
continue;
|
|
2972
3189
|
}
|
|
2973
|
-
lines.push(" </tool>");
|
|
2974
3190
|
}
|
|
2975
|
-
return
|
|
3191
|
+
return null;
|
|
2976
3192
|
}
|
|
2977
|
-
function
|
|
2978
|
-
const
|
|
2979
|
-
if (
|
|
2980
|
-
return
|
|
3193
|
+
function loadSoul() {
|
|
3194
|
+
const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
|
|
3195
|
+
if (soul) {
|
|
3196
|
+
return soul;
|
|
2981
3197
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3198
|
+
logWarn(
|
|
3199
|
+
"soul_load_fallback",
|
|
3200
|
+
{},
|
|
3201
|
+
{
|
|
3202
|
+
"app.file.candidates": soulPathCandidates()
|
|
3203
|
+
},
|
|
3204
|
+
"SOUL.md not found; using built-in default personality"
|
|
3205
|
+
);
|
|
3206
|
+
return DEFAULT_SOUL;
|
|
2986
3207
|
}
|
|
2987
|
-
function
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3208
|
+
function loadWorld() {
|
|
3209
|
+
return loadOptionalMarkdownFile(worldPathCandidates(), "WORLD.md");
|
|
3210
|
+
}
|
|
3211
|
+
var JUNIOR_PERSONALITY = (() => {
|
|
3212
|
+
try {
|
|
3213
|
+
return loadSoul();
|
|
3214
|
+
} catch (error) {
|
|
3215
|
+
logWarn(
|
|
3216
|
+
"soul_load_failed",
|
|
3217
|
+
{},
|
|
3218
|
+
{
|
|
3219
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
3220
|
+
},
|
|
3221
|
+
"Failed to load SOUL.md; using built-in default personality"
|
|
3222
|
+
);
|
|
3223
|
+
return DEFAULT_SOUL;
|
|
2992
3224
|
}
|
|
2993
|
-
|
|
2994
|
-
|
|
3225
|
+
})();
|
|
3226
|
+
var JUNIOR_WORLD = (() => {
|
|
3227
|
+
try {
|
|
3228
|
+
return loadWorld();
|
|
3229
|
+
} catch (error) {
|
|
3230
|
+
logWarn(
|
|
3231
|
+
"world_load_failed",
|
|
3232
|
+
{},
|
|
3233
|
+
{
|
|
3234
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
3235
|
+
},
|
|
3236
|
+
"Failed to load WORLD.md; omitting world prompt context"
|
|
3237
|
+
);
|
|
3238
|
+
return null;
|
|
2995
3239
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3240
|
+
})();
|
|
3241
|
+
function workspaceSkillDir(skillName) {
|
|
3242
|
+
return sandboxSkillDir(skillName);
|
|
3243
|
+
}
|
|
3244
|
+
function formatConfigurationValue(value) {
|
|
3245
|
+
if (typeof value === "string") {
|
|
3246
|
+
return escapeXml(value);
|
|
3006
3247
|
}
|
|
3007
|
-
|
|
3008
|
-
|
|
3248
|
+
try {
|
|
3249
|
+
return escapeXml(JSON.stringify(value));
|
|
3250
|
+
} catch {
|
|
3251
|
+
return escapeXml(String(value));
|
|
3009
3252
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3253
|
+
}
|
|
3254
|
+
function renderRequesterBlock(fields) {
|
|
3255
|
+
const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key, value]) => `- ${key}: ${escapeXml(value)}`);
|
|
3256
|
+
if (lines.length === 0) {
|
|
3257
|
+
return null;
|
|
3012
3258
|
}
|
|
3013
|
-
return
|
|
3259
|
+
return ["<requester>", ...lines, "</requester>"];
|
|
3014
3260
|
}
|
|
3015
|
-
function
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3261
|
+
function renderTag(tag, lines) {
|
|
3262
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
3263
|
+
}
|
|
3264
|
+
function renderTagBlock(tag, content) {
|
|
3265
|
+
return [`<${tag}>`, content, `</${tag}>`].join("\n");
|
|
3266
|
+
}
|
|
3267
|
+
function formatSkillEntry(skill) {
|
|
3268
|
+
const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
|
|
3269
|
+
const lines = [];
|
|
3270
|
+
lines.push(" <skill>");
|
|
3271
|
+
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
3272
|
+
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
3273
|
+
lines.push(` <location>${escapeXml(skillLocation)}</location>`);
|
|
3274
|
+
if (skill.pluginProvider) {
|
|
3275
|
+
lines.push(` <provider>${escapeXml(skill.pluginProvider)}</provider>`);
|
|
3276
|
+
}
|
|
3277
|
+
lines.push(" </skill>");
|
|
3278
|
+
return lines;
|
|
3279
|
+
}
|
|
3280
|
+
function formatAvailableSkillsForPrompt(skills, invocation) {
|
|
3281
|
+
const autoSelectable = skills.filter(
|
|
3282
|
+
(s) => s.disableModelInvocation !== true
|
|
3022
3283
|
);
|
|
3284
|
+
const invokedExplicitOnly = invocation ? skills.filter(
|
|
3285
|
+
(s) => s.disableModelInvocation === true && s.name === invocation.skillName
|
|
3286
|
+
) : [];
|
|
3287
|
+
const sections = [];
|
|
3288
|
+
if (autoSelectable.length > 0) {
|
|
3289
|
+
const available = [
|
|
3290
|
+
"<available-skills>",
|
|
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."
|
|
3292
|
+
];
|
|
3293
|
+
for (const skill of autoSelectable) {
|
|
3294
|
+
available.push(...formatSkillEntry(skill));
|
|
3295
|
+
}
|
|
3296
|
+
available.push("</available-skills>");
|
|
3297
|
+
sections.push(available.join("\n"));
|
|
3298
|
+
}
|
|
3299
|
+
if (invokedExplicitOnly.length > 0) {
|
|
3300
|
+
const userCallable = [
|
|
3301
|
+
"<user-callable-skills>",
|
|
3302
|
+
"The user's current message explicitly references this skill by name. Load it when relevant to the request."
|
|
3303
|
+
];
|
|
3304
|
+
for (const skill of invokedExplicitOnly) {
|
|
3305
|
+
userCallable.push(...formatSkillEntry(skill));
|
|
3306
|
+
}
|
|
3307
|
+
userCallable.push("</user-callable-skills>");
|
|
3308
|
+
sections.push(userCallable.join("\n"));
|
|
3309
|
+
}
|
|
3310
|
+
return sections.length > 0 ? sections.join("\n") : null;
|
|
3023
3311
|
}
|
|
3024
|
-
function
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3312
|
+
function formatLoadedSkillsForPrompt(skills) {
|
|
3313
|
+
if (skills.length === 0) {
|
|
3314
|
+
return null;
|
|
3315
|
+
}
|
|
3316
|
+
const lines = ["<loaded-skills>"];
|
|
3317
|
+
for (const skill of skills) {
|
|
3318
|
+
const skillDir = workspaceSkillDir(skill.name);
|
|
3319
|
+
lines.push(
|
|
3320
|
+
` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
|
|
3321
|
+
);
|
|
3322
|
+
lines.push(
|
|
3323
|
+
`Skill directory: ${escapeXml(skillDir)}. Resolve relative paths there; for skill-owned bash commands, cd there first or use absolute paths.`
|
|
3324
|
+
);
|
|
3325
|
+
lines.push("");
|
|
3326
|
+
lines.push(skill.body);
|
|
3327
|
+
lines.push(" </skill>");
|
|
3328
|
+
}
|
|
3329
|
+
lines.push("</loaded-skills>");
|
|
3330
|
+
return lines.join("\n");
|
|
3031
3331
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3332
|
+
function formatProviderCatalogForPrompt() {
|
|
3333
|
+
const providers = getPluginProviders().map((plugin) => plugin.manifest);
|
|
3334
|
+
if (providers.length === 0) {
|
|
3335
|
+
return null;
|
|
3336
|
+
}
|
|
3337
|
+
const lines = [
|
|
3338
|
+
"Config keys and default targets per provider; use after a skill is loaded. Run authenticated provider commands directly after resolving target defaults; let the runtime handle auth pauses/resumes."
|
|
3339
|
+
];
|
|
3340
|
+
for (const provider of providers) {
|
|
3341
|
+
lines.push(`- provider: ${escapeXml(provider.name)}`);
|
|
3342
|
+
lines.push(
|
|
3343
|
+
` - config_keys: ${provider.configKeys.length > 0 ? escapeXml(provider.configKeys.join(", ")) : "none"}`
|
|
3344
|
+
);
|
|
3345
|
+
lines.push(
|
|
3346
|
+
` - default_context: ${provider.target ? escapeXml(
|
|
3347
|
+
`${provider.target.type} via ${provider.target.configKey}`
|
|
3348
|
+
) : "none"}`
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
return lines.join("\n");
|
|
3352
|
+
}
|
|
3353
|
+
function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
3354
|
+
if (catalogs.length === 0) {
|
|
3355
|
+
return null;
|
|
3356
|
+
}
|
|
3357
|
+
const lines = [
|
|
3358
|
+
"Active MCP provider catalogs are available through `searchMcpTools`. Call it with provider to list descriptors or with query to narrow results, then pass the exact returned `tool_name` to `callMcpTool`. Put provider fields inside `arguments`."
|
|
3359
|
+
];
|
|
3360
|
+
for (const catalog of catalogs) {
|
|
3361
|
+
lines.push(" <catalog>");
|
|
3362
|
+
lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
|
|
3363
|
+
lines.push(
|
|
3364
|
+
` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
|
|
3365
|
+
);
|
|
3366
|
+
lines.push(" </catalog>");
|
|
3367
|
+
}
|
|
3368
|
+
return lines.join("\n");
|
|
3369
|
+
}
|
|
3370
|
+
function formatToolGuidanceForPrompt(tools) {
|
|
3371
|
+
const guidedTools = tools.filter(
|
|
3372
|
+
(tool2) => Boolean(tool2.promptSnippet?.trim()) || (tool2.promptGuidelines?.length ?? 0) > 0
|
|
3373
|
+
);
|
|
3374
|
+
if (guidedTools.length === 0) {
|
|
3375
|
+
return null;
|
|
3376
|
+
}
|
|
3377
|
+
const lines = [];
|
|
3378
|
+
for (const tool2 of guidedTools) {
|
|
3379
|
+
lines.push(` <tool name="${escapeXml(tool2.name)}">`);
|
|
3380
|
+
if (tool2.promptSnippet?.trim()) {
|
|
3381
|
+
lines.push(` - ${escapeXml(tool2.promptSnippet.trim())}`);
|
|
3382
|
+
}
|
|
3383
|
+
if (tool2.promptGuidelines && tool2.promptGuidelines.length > 0) {
|
|
3384
|
+
for (const guideline of tool2.promptGuidelines) {
|
|
3385
|
+
lines.push(` - ${escapeXml(guideline)}`);
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
lines.push(" </tool>");
|
|
3389
|
+
}
|
|
3390
|
+
return lines.join("\n");
|
|
3391
|
+
}
|
|
3392
|
+
function formatReferenceFilesLines() {
|
|
3393
|
+
const files = listReferenceFiles();
|
|
3394
|
+
if (files.length === 0) {
|
|
3395
|
+
return null;
|
|
3396
|
+
}
|
|
3397
|
+
return files.map((filePath) => {
|
|
3398
|
+
const name = path2.basename(filePath);
|
|
3399
|
+
return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
|
|
3400
|
+
});
|
|
3401
|
+
}
|
|
3402
|
+
function formatArtifactsLines(artifactState) {
|
|
3403
|
+
if (!artifactState) return null;
|
|
3404
|
+
const lines = [];
|
|
3405
|
+
if (artifactState.lastCanvasId) {
|
|
3406
|
+
lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
|
|
3407
|
+
}
|
|
3408
|
+
if (artifactState.lastCanvasUrl) {
|
|
3409
|
+
lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
|
|
3410
|
+
}
|
|
3411
|
+
if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
|
|
3412
|
+
lines.push("- recent_canvases:");
|
|
3413
|
+
for (const canvas of artifactState.recentCanvases) {
|
|
3414
|
+
lines.push(` - id: ${escapeXml(canvas.id)}`);
|
|
3415
|
+
if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
|
|
3416
|
+
if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
|
|
3417
|
+
if (canvas.createdAt) {
|
|
3418
|
+
lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
if (artifactState.lastListId) {
|
|
3423
|
+
lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
|
|
3424
|
+
}
|
|
3425
|
+
if (artifactState.lastListUrl) {
|
|
3426
|
+
lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
|
|
3427
|
+
}
|
|
3428
|
+
return lines.length > 0 ? lines : null;
|
|
3429
|
+
}
|
|
3430
|
+
function formatConfigurationLines(configuration) {
|
|
3431
|
+
const keys = Object.keys(configuration ?? {}).sort(
|
|
3432
|
+
(a, b) => a.localeCompare(b)
|
|
3433
|
+
);
|
|
3434
|
+
if (keys.length === 0) return null;
|
|
3435
|
+
return keys.map(
|
|
3436
|
+
(key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
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.";
|
|
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.";
|
|
3441
|
+
var TOOL_POLICY_RULES = [
|
|
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.",
|
|
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.",
|
|
3037
3444
|
"- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
|
|
3038
3445
|
"- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
|
|
3039
3446
|
"- For repository or implementation questions, inspect the target repository first: local checkout when present, otherwise the configured GitHub/source provider. Do not treat loaded skill files as repo source unless the user asks about the skill. Cite file paths, symbols, PRs/issues, commits, or URLs that support the answer.",
|
|
@@ -3118,16 +3525,12 @@ function buildIdentitySection() {
|
|
|
3118
3525
|
}
|
|
3119
3526
|
function buildRuntimeSection(params) {
|
|
3120
3527
|
const lines = [
|
|
3121
|
-
`-
|
|
3122
|
-
params.
|
|
3123
|
-
params.fastModelId ? `- fast_model: ${escapeXml(params.fastModelId)}` : "",
|
|
3124
|
-
params.thinkingLevel ? `- thinking: ${escapeXml(params.thinkingLevel)}` : "",
|
|
3125
|
-
params.channelId ? "- channel: slack" : "",
|
|
3126
|
-
params.channelId ? `- slack_capabilities: ${escapeXml(
|
|
3127
|
-
formatSlackCapabilityNames(params.slackCapabilities)
|
|
3128
|
-
)}` : "",
|
|
3129
|
-
`- sandbox_workspace: ${escapeXml(SANDBOX_WORKSPACE_ROOT)}`
|
|
3528
|
+
params.conversationId ? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}` : "",
|
|
3529
|
+
params.traceId ? `- trace_id: ${escapeXml(params.traceId)}` : ""
|
|
3130
3530
|
].filter(Boolean);
|
|
3531
|
+
if (lines.length === 0) {
|
|
3532
|
+
return null;
|
|
3533
|
+
}
|
|
3131
3534
|
return renderTagBlock("runtime", lines.join("\n"));
|
|
3132
3535
|
}
|
|
3133
3536
|
function buildContextSection(params) {
|
|
@@ -3180,14 +3583,24 @@ function buildContextSection(params) {
|
|
|
3180
3583
|
);
|
|
3181
3584
|
}
|
|
3182
3585
|
const body = blocks.map((block) => block.join("\n")).join("\n\n");
|
|
3586
|
+
if (!body) {
|
|
3587
|
+
return null;
|
|
3588
|
+
}
|
|
3183
3589
|
return renderTagBlock("context", body);
|
|
3184
3590
|
}
|
|
3185
3591
|
function buildCapabilitiesSection(params) {
|
|
3186
3592
|
const blocks = [];
|
|
3187
|
-
|
|
3188
|
-
|
|
3593
|
+
const availableSkills = formatAvailableSkillsForPrompt(
|
|
3594
|
+
params.availableSkills,
|
|
3595
|
+
params.invocation
|
|
3189
3596
|
);
|
|
3190
|
-
|
|
3597
|
+
if (availableSkills) {
|
|
3598
|
+
blocks.push(availableSkills);
|
|
3599
|
+
}
|
|
3600
|
+
const loadedSkills = formatLoadedSkillsForPrompt(params.activeSkills);
|
|
3601
|
+
if (loadedSkills) {
|
|
3602
|
+
blocks.push(loadedSkills);
|
|
3603
|
+
}
|
|
3191
3604
|
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
3192
3605
|
params.activeMcpCatalogs
|
|
3193
3606
|
);
|
|
@@ -3202,6 +3615,9 @@ function buildCapabilitiesSection(params) {
|
|
|
3202
3615
|
if (providerCatalog) {
|
|
3203
3616
|
blocks.push(renderTagBlock("providers", providerCatalog));
|
|
3204
3617
|
}
|
|
3618
|
+
if (blocks.length === 0) {
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3205
3621
|
return renderTagBlock("capabilities", blocks.join("\n\n"));
|
|
3206
3622
|
}
|
|
3207
3623
|
var STATIC_SYSTEM_PROMPT = [
|
|
@@ -3235,7 +3651,7 @@ function buildTurnContextPrompt(params) {
|
|
|
3235
3651
|
}),
|
|
3236
3652
|
buildRuntimeSection(params.runtime ?? {}),
|
|
3237
3653
|
`</${TURN_CONTEXT_TAG}>`
|
|
3238
|
-
];
|
|
3654
|
+
].filter((section) => Boolean(section));
|
|
3239
3655
|
return sections.join("\n\n");
|
|
3240
3656
|
}
|
|
3241
3657
|
|
|
@@ -3949,6 +4365,8 @@ var PluginMcpClient = class {
|
|
|
3949
4365
|
this.plugin = plugin;
|
|
3950
4366
|
this.options = options;
|
|
3951
4367
|
}
|
|
4368
|
+
plugin;
|
|
4369
|
+
options;
|
|
3952
4370
|
client;
|
|
3953
4371
|
lastAttemptedTransportSessionId;
|
|
3954
4372
|
transport;
|
|
@@ -4230,6 +4648,7 @@ var McpToolManager = class {
|
|
|
4230
4648
|
}
|
|
4231
4649
|
}
|
|
4232
4650
|
}
|
|
4651
|
+
options;
|
|
4233
4652
|
pluginsByProvider = /* @__PURE__ */ new Map();
|
|
4234
4653
|
activeProviders = /* @__PURE__ */ new Set();
|
|
4235
4654
|
authorizationPendingProviders = /* @__PURE__ */ new Set();
|
|
@@ -7887,7 +8306,7 @@ function createSystemTimeTool() {
|
|
|
7887
8306
|
// src/chat/tools/advisor/tool.ts
|
|
7888
8307
|
import {
|
|
7889
8308
|
Agent
|
|
7890
|
-
} from "@
|
|
8309
|
+
} from "@earendil-works/pi-agent-core";
|
|
7891
8310
|
import { Type as Type21 } from "@sinclair/typebox";
|
|
7892
8311
|
|
|
7893
8312
|
// src/chat/respond-helpers.ts
|
|
@@ -7972,44 +8391,20 @@ function summarizeMessageText(text) {
|
|
|
7972
8391
|
}
|
|
7973
8392
|
return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
|
|
7974
8393
|
}
|
|
7975
|
-
function buildUserTurnText(userInput, conversationContext
|
|
8394
|
+
function buildUserTurnText(userInput, conversationContext) {
|
|
7976
8395
|
const trimmedContext = conversationContext?.trim();
|
|
7977
|
-
|
|
7978
|
-
const traceId = metadata?.turnContext?.traceId;
|
|
7979
|
-
if (!trimmedContext && !conversationId && !traceId) {
|
|
8396
|
+
if (!trimmedContext) {
|
|
7980
8397
|
return userInput;
|
|
7981
8398
|
}
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
""
|
|
7989
|
-
);
|
|
7990
|
-
}
|
|
7991
|
-
if (conversationId) {
|
|
7992
|
-
sections.push(
|
|
7993
|
-
"<session-context>",
|
|
7994
|
-
`- gen_ai.conversation.id: ${conversationId}`,
|
|
7995
|
-
"</session-context>",
|
|
7996
|
-
""
|
|
7997
|
-
);
|
|
7998
|
-
}
|
|
7999
|
-
if (traceId) {
|
|
8000
|
-
sections.push(
|
|
8001
|
-
"<turn-context>",
|
|
8002
|
-
`- trace_id: ${traceId}`,
|
|
8003
|
-
"</turn-context>",
|
|
8004
|
-
""
|
|
8005
|
-
);
|
|
8006
|
-
}
|
|
8007
|
-
sections.push(
|
|
8008
|
-
'<current-instruction priority="highest">',
|
|
8399
|
+
return [
|
|
8400
|
+
"<thread-background>",
|
|
8401
|
+
trimmedContext,
|
|
8402
|
+
"</thread-background>",
|
|
8403
|
+
"",
|
|
8404
|
+
"<current-instruction>",
|
|
8009
8405
|
userInput,
|
|
8010
8406
|
"</current-instruction>"
|
|
8011
|
-
);
|
|
8012
|
-
return sections.join("\n");
|
|
8407
|
+
].join("\n");
|
|
8013
8408
|
}
|
|
8014
8409
|
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
8015
8410
|
const base64 = attachment.data.toString("base64");
|
|
@@ -8161,8 +8556,8 @@ function trimTrailingAssistantMessages(messages) {
|
|
|
8161
8556
|
}
|
|
8162
8557
|
|
|
8163
8558
|
// src/chat/tools/advisor/session-store.ts
|
|
8164
|
-
import { THREAD_STATE_TTL_MS as
|
|
8165
|
-
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;
|
|
8166
8561
|
function cloneMessages(messages) {
|
|
8167
8562
|
return structuredClone(messages);
|
|
8168
8563
|
}
|
|
@@ -9137,7 +9532,7 @@ function resolveChannelCapabilities(channelId) {
|
|
|
9137
9532
|
// src/chat/pi/traced-stream.ts
|
|
9138
9533
|
import {
|
|
9139
9534
|
streamSimple
|
|
9140
|
-
} from "@
|
|
9535
|
+
} from "@earendil-works/pi-ai";
|
|
9141
9536
|
function buildChatStartAttributes(model, context) {
|
|
9142
9537
|
const attributes = {
|
|
9143
9538
|
"gen_ai.operation.name": "chat",
|
|
@@ -9222,23 +9617,18 @@ import fs4 from "fs/promises";
|
|
|
9222
9617
|
import { createHmac, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
|
|
9223
9618
|
var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
|
|
9224
9619
|
var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
|
|
9620
|
+
var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
|
|
9225
9621
|
var SANDBOX_EGRESS_LEASE_PREFIX = "sandbox-egress-lease";
|
|
9226
9622
|
var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
9227
9623
|
function leaseKey(provider, context) {
|
|
9228
9624
|
return `${SANDBOX_EGRESS_LEASE_PREFIX}:${provider}:${context.requesterId}:${context.egressId}:${context.contextId}`;
|
|
9229
9625
|
}
|
|
9230
9626
|
function getSandboxEgressSecret() {
|
|
9231
|
-
const
|
|
9232
|
-
if (
|
|
9233
|
-
return
|
|
9234
|
-
}
|
|
9235
|
-
const sharedInternal = process.env.JUNIOR_INTERNAL_RESUME_SECRET?.trim();
|
|
9236
|
-
if (sharedInternal) {
|
|
9237
|
-
return sharedInternal;
|
|
9627
|
+
const secret = process.env.JUNIOR_SECRET?.trim();
|
|
9628
|
+
if (secret) {
|
|
9629
|
+
return secret;
|
|
9238
9630
|
}
|
|
9239
|
-
throw new Error(
|
|
9240
|
-
"Cannot determine sandbox egress secret (set JUNIOR_SANDBOX_EGRESS_SECRET or JUNIOR_INTERNAL_RESUME_SECRET)"
|
|
9241
|
-
);
|
|
9631
|
+
throw new Error("Cannot determine sandbox egress secret (set JUNIOR_SECRET)");
|
|
9242
9632
|
}
|
|
9243
9633
|
function base64Url(input) {
|
|
9244
9634
|
return Buffer.from(input, "utf8").toString("base64url");
|
|
@@ -9247,7 +9637,7 @@ function fromBase64Url(input) {
|
|
|
9247
9637
|
return Buffer.from(input, "base64url").toString("utf8");
|
|
9248
9638
|
}
|
|
9249
9639
|
function signPayload(payload) {
|
|
9250
|
-
return createHmac("sha256", getSandboxEgressSecret()).update(payload).digest("base64url");
|
|
9640
|
+
return createHmac("sha256", getSandboxEgressSecret()).update(`${SANDBOX_EGRESS_HMAC_CONTEXT}:${payload}`).digest("base64url");
|
|
9251
9641
|
}
|
|
9252
9642
|
function timingSafeMatch(expected, actual) {
|
|
9253
9643
|
const expectedBuffer = Buffer.from(expected);
|
|
@@ -11249,500 +11639,161 @@ function createSandboxExecutor(options) {
|
|
|
11249
11639
|
referenceFiles = [...files];
|
|
11250
11640
|
sessionManager.configureReferenceFiles(files);
|
|
11251
11641
|
},
|
|
11252
|
-
getSandboxId() {
|
|
11253
|
-
return sessionManager.getSandboxId();
|
|
11254
|
-
},
|
|
11255
|
-
getDependencyProfileHash() {
|
|
11256
|
-
return sessionManager.getDependencyProfileHash();
|
|
11257
|
-
},
|
|
11258
|
-
canExecute(toolName) {
|
|
11259
|
-
return SANDBOX_TOOL_NAMES.has(toolName);
|
|
11260
|
-
},
|
|
11261
|
-
async createSandbox() {
|
|
11262
|
-
return await sessionManager.createSandbox();
|
|
11263
|
-
},
|
|
11264
|
-
execute,
|
|
11265
|
-
async dispose() {
|
|
11266
|
-
await sessionManager.dispose();
|
|
11267
|
-
}
|
|
11268
|
-
};
|
|
11269
|
-
}
|
|
11270
|
-
|
|
11271
|
-
// src/chat/runtime/dev-agent-trace.ts
|
|
11272
|
-
function shouldEmitDevAgentTrace() {
|
|
11273
|
-
return process.env.NODE_ENV === "development";
|
|
11274
|
-
}
|
|
11275
|
-
|
|
11276
|
-
// src/chat/services/auth-pause.ts
|
|
11277
|
-
var AuthorizationPauseError = class extends Error {
|
|
11278
|
-
disposition;
|
|
11279
|
-
kind;
|
|
11280
|
-
provider;
|
|
11281
|
-
constructor(kind, provider, disposition) {
|
|
11282
|
-
super(
|
|
11283
|
-
kind === "mcp" ? `MCP authorization started for ${provider}` : `Plugin authorization started for ${provider}`
|
|
11284
|
-
);
|
|
11285
|
-
this.name = kind === "mcp" ? "McpAuthorizationPauseError" : "PluginAuthorizationPauseError";
|
|
11286
|
-
this.disposition = disposition;
|
|
11287
|
-
this.kind = kind;
|
|
11288
|
-
this.provider = provider;
|
|
11289
|
-
}
|
|
11290
|
-
};
|
|
11291
|
-
|
|
11292
|
-
// src/chat/runtime/report-progress.ts
|
|
11293
|
-
function buildReportedProgressStatus(input) {
|
|
11294
|
-
if (!input || typeof input !== "object") {
|
|
11295
|
-
return void 0;
|
|
11296
|
-
}
|
|
11297
|
-
const message = input.message;
|
|
11298
|
-
if (typeof message !== "string") {
|
|
11299
|
-
return void 0;
|
|
11300
|
-
}
|
|
11301
|
-
const text = message.trim();
|
|
11302
|
-
if (!text) {
|
|
11303
|
-
return void 0;
|
|
11304
|
-
}
|
|
11305
|
-
return { text };
|
|
11306
|
-
}
|
|
11307
|
-
|
|
11308
|
-
// src/chat/tools/execution/build-sandbox-input.ts
|
|
11309
|
-
function buildSandboxInput(toolName, params) {
|
|
11310
|
-
const optionalNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11311
|
-
if (toolName === "bash") {
|
|
11312
|
-
return {
|
|
11313
|
-
command: String(params.command ?? ""),
|
|
11314
|
-
...optionalNumber(params.timeoutMs) ? { timeoutMs: optionalNumber(params.timeoutMs) } : {}
|
|
11315
|
-
};
|
|
11316
|
-
}
|
|
11317
|
-
if (toolName === "readFile") {
|
|
11318
|
-
return {
|
|
11319
|
-
path: String(params.path ?? ""),
|
|
11320
|
-
...optionalNumber(params.offset) ? { offset: optionalNumber(params.offset) } : {},
|
|
11321
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11322
|
-
};
|
|
11323
|
-
}
|
|
11324
|
-
if (toolName === "editFile") {
|
|
11325
|
-
return {
|
|
11326
|
-
path: String(params.path ?? ""),
|
|
11327
|
-
edits: Array.isArray(params.edits) ? params.edits : []
|
|
11328
|
-
};
|
|
11329
|
-
}
|
|
11330
|
-
if (toolName === "grep") {
|
|
11331
|
-
return {
|
|
11332
|
-
pattern: String(params.pattern ?? ""),
|
|
11333
|
-
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11334
|
-
...typeof params.glob === "string" ? { glob: params.glob } : {},
|
|
11335
|
-
...typeof params.ignoreCase === "boolean" ? { ignoreCase: params.ignoreCase } : {},
|
|
11336
|
-
...typeof params.literal === "boolean" ? { literal: params.literal } : {},
|
|
11337
|
-
...optionalNumber(params.context) ? { context: optionalNumber(params.context) } : {},
|
|
11338
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11339
|
-
};
|
|
11340
|
-
}
|
|
11341
|
-
if (toolName === "findFiles") {
|
|
11342
|
-
return {
|
|
11343
|
-
pattern: String(params.pattern ?? ""),
|
|
11344
|
-
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11345
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11346
|
-
};
|
|
11347
|
-
}
|
|
11348
|
-
if (toolName === "listDir") {
|
|
11349
|
-
return {
|
|
11350
|
-
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11351
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11352
|
-
};
|
|
11353
|
-
}
|
|
11354
|
-
if (toolName === "writeFile") {
|
|
11355
|
-
return {
|
|
11356
|
-
path: String(params.path ?? ""),
|
|
11357
|
-
content: String(params.content ?? "")
|
|
11358
|
-
};
|
|
11359
|
-
}
|
|
11360
|
-
return params;
|
|
11361
|
-
}
|
|
11362
|
-
|
|
11363
|
-
// src/chat/tools/execution/normalize-result.ts
|
|
11364
|
-
function isStructuredToolExecutionResult(value) {
|
|
11365
|
-
const content = value?.content;
|
|
11366
|
-
return typeof value === "object" && value !== null && Array.isArray(content) && content.every((part) => {
|
|
11367
|
-
if (!part || typeof part !== "object") {
|
|
11368
|
-
return false;
|
|
11369
|
-
}
|
|
11370
|
-
const record = part;
|
|
11371
|
-
if (record.type === "text") {
|
|
11372
|
-
return typeof record.text === "string";
|
|
11373
|
-
}
|
|
11374
|
-
if (record.type === "image") {
|
|
11375
|
-
return typeof record.data === "string" && typeof record.mimeType === "string";
|
|
11376
|
-
}
|
|
11377
|
-
return false;
|
|
11378
|
-
}) && "details" in value;
|
|
11379
|
-
}
|
|
11380
|
-
function toToolContentText(value) {
|
|
11381
|
-
if (typeof value === "string") return value;
|
|
11382
|
-
try {
|
|
11383
|
-
return JSON.stringify(value);
|
|
11384
|
-
} catch {
|
|
11385
|
-
return String(value);
|
|
11386
|
-
}
|
|
11387
|
-
}
|
|
11388
|
-
function normalizeToolResult(result, isSandboxResult) {
|
|
11389
|
-
const unwrapped = isSandboxResult && result && typeof result === "object" && "result" in result ? result.result : result;
|
|
11390
|
-
if (isStructuredToolExecutionResult(unwrapped)) {
|
|
11391
|
-
return unwrapped;
|
|
11392
|
-
}
|
|
11393
|
-
return {
|
|
11394
|
-
content: [{ type: "text", text: toToolContentText(unwrapped) }],
|
|
11395
|
-
details: unwrapped
|
|
11396
|
-
};
|
|
11397
|
-
}
|
|
11398
|
-
|
|
11399
|
-
// src/chat/credentials/unlink-provider.ts
|
|
11400
|
-
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
11401
|
-
await Promise.all([
|
|
11402
|
-
userTokenStore.delete(userId, provider),
|
|
11403
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
11404
|
-
deleteMcpServerSessionId(userId, provider),
|
|
11405
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
11406
|
-
]);
|
|
11407
|
-
}
|
|
11408
|
-
|
|
11409
|
-
// src/chat/state/turn-session-store.ts
|
|
11410
|
-
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
11411
|
-
|
|
11412
|
-
// src/chat/state/pi-session-message-store.ts
|
|
11413
|
-
import { isDeepStrictEqual } from "util";
|
|
11414
|
-
var PI_SESSION_MESSAGE_PREFIX = "junior:pi_session_message";
|
|
11415
|
-
function piSessionMessageKey(scope, index) {
|
|
11416
|
-
return `${PI_SESSION_MESSAGE_PREFIX}:${scope.conversationId}:${scope.sessionId}:${index}`;
|
|
11417
|
-
}
|
|
11418
|
-
function parsePiMessage(value) {
|
|
11419
|
-
return isRecord(value) ? value : void 0;
|
|
11420
|
-
}
|
|
11421
|
-
function normalizeMessageCount(value) {
|
|
11422
|
-
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
11423
|
-
}
|
|
11424
|
-
function countMatchingPrefix(left, right) {
|
|
11425
|
-
const limit = Math.min(left.length, right.length);
|
|
11426
|
-
for (let index = 0; index < limit; index += 1) {
|
|
11427
|
-
if (!isDeepStrictEqual(left[index], right[index])) {
|
|
11428
|
-
return index;
|
|
11429
|
-
}
|
|
11430
|
-
}
|
|
11431
|
-
return limit;
|
|
11432
|
-
}
|
|
11433
|
-
async function loadPiSessionMessages(args) {
|
|
11434
|
-
const stateAdapter = getStateAdapter();
|
|
11435
|
-
await stateAdapter.connect();
|
|
11436
|
-
const messageCount = normalizeMessageCount(args.messageCount);
|
|
11437
|
-
if (messageCount === 0) {
|
|
11438
|
-
return [];
|
|
11439
|
-
}
|
|
11440
|
-
const values = await Promise.all(
|
|
11441
|
-
Array.from(
|
|
11442
|
-
{ length: messageCount },
|
|
11443
|
-
(_, index) => stateAdapter.get(piSessionMessageKey(args, index))
|
|
11444
|
-
)
|
|
11445
|
-
);
|
|
11446
|
-
const messages = [];
|
|
11447
|
-
for (const value of values) {
|
|
11448
|
-
const message = parsePiMessage(value);
|
|
11449
|
-
if (!message) {
|
|
11450
|
-
break;
|
|
11451
|
-
}
|
|
11452
|
-
messages.push(message);
|
|
11453
|
-
}
|
|
11454
|
-
return messages.length === messageCount ? messages : void 0;
|
|
11455
|
-
}
|
|
11456
|
-
async function loadExistingPiSessionMessages(scope, maxCount) {
|
|
11457
|
-
const count = normalizeMessageCount(maxCount);
|
|
11458
|
-
if (count === 0) {
|
|
11459
|
-
return [];
|
|
11460
|
-
}
|
|
11461
|
-
const stateAdapter = getStateAdapter();
|
|
11462
|
-
await stateAdapter.connect();
|
|
11463
|
-
const values = await Promise.all(
|
|
11464
|
-
Array.from(
|
|
11465
|
-
{ length: count },
|
|
11466
|
-
(_, index) => stateAdapter.get(piSessionMessageKey(scope, index))
|
|
11467
|
-
)
|
|
11468
|
-
);
|
|
11469
|
-
const messages = [];
|
|
11470
|
-
for (const value of values) {
|
|
11471
|
-
const message = parsePiMessage(value);
|
|
11472
|
-
if (!message) {
|
|
11473
|
-
break;
|
|
11474
|
-
}
|
|
11475
|
-
messages.push(message);
|
|
11476
|
-
}
|
|
11477
|
-
return messages;
|
|
11478
|
-
}
|
|
11479
|
-
async function commitPiSessionMessages(args) {
|
|
11480
|
-
const stateAdapter = getStateAdapter();
|
|
11481
|
-
await stateAdapter.connect();
|
|
11482
|
-
const existingMessages = await loadExistingPiSessionMessages(
|
|
11483
|
-
{ conversationId: args.conversationId, sessionId: args.sessionId },
|
|
11484
|
-
args.messages.length
|
|
11485
|
-
);
|
|
11486
|
-
const writeFromIndex = countMatchingPrefix(existingMessages, args.messages);
|
|
11487
|
-
await Promise.all(
|
|
11488
|
-
args.messages.slice(writeFromIndex).map(
|
|
11489
|
-
(message, offset) => stateAdapter.set(
|
|
11490
|
-
piSessionMessageKey(args, writeFromIndex + offset),
|
|
11491
|
-
message,
|
|
11492
|
-
args.ttlMs
|
|
11493
|
-
)
|
|
11494
|
-
)
|
|
11495
|
-
);
|
|
11496
|
-
}
|
|
11497
|
-
|
|
11498
|
-
// src/chat/state/turn-session-store.ts
|
|
11499
|
-
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
11500
|
-
var AGENT_TURN_SESSION_TTL_MS = THREAD_STATE_TTL_MS3;
|
|
11501
|
-
function agentTurnSessionKey(conversationId, sessionId) {
|
|
11502
|
-
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
11503
|
-
}
|
|
11504
|
-
function toFiniteNonNegativeNumber(value) {
|
|
11505
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
11506
|
-
}
|
|
11507
|
-
function parseAgentTurnUsage(value) {
|
|
11508
|
-
if (!isRecord(value)) {
|
|
11509
|
-
return void 0;
|
|
11510
|
-
}
|
|
11511
|
-
const usage = {};
|
|
11512
|
-
for (const field of [
|
|
11513
|
-
"inputTokens",
|
|
11514
|
-
"outputTokens",
|
|
11515
|
-
"cachedInputTokens",
|
|
11516
|
-
"cacheCreationTokens",
|
|
11517
|
-
"totalTokens"
|
|
11518
|
-
]) {
|
|
11519
|
-
const count = toFiniteNonNegativeNumber(value[field]);
|
|
11520
|
-
if (count !== void 0) {
|
|
11521
|
-
usage[field] = count;
|
|
11522
|
-
}
|
|
11523
|
-
}
|
|
11524
|
-
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
11525
|
-
}
|
|
11526
|
-
function parseStoredRecord(value) {
|
|
11527
|
-
if (isRecord(value)) {
|
|
11528
|
-
return value;
|
|
11529
|
-
}
|
|
11530
|
-
if (typeof value !== "string") {
|
|
11531
|
-
return void 0;
|
|
11532
|
-
}
|
|
11533
|
-
try {
|
|
11534
|
-
const parsed = JSON.parse(value);
|
|
11535
|
-
return isRecord(parsed) ? parsed : void 0;
|
|
11536
|
-
} catch {
|
|
11537
|
-
return void 0;
|
|
11538
|
-
}
|
|
11539
|
-
}
|
|
11540
|
-
function parseAgentTurnSessionRecord(value) {
|
|
11541
|
-
const parsed = parseStoredRecord(value);
|
|
11542
|
-
if (!parsed) {
|
|
11543
|
-
return void 0;
|
|
11544
|
-
}
|
|
11545
|
-
const status = parsed.state;
|
|
11546
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
11547
|
-
return void 0;
|
|
11548
|
-
}
|
|
11549
|
-
const conversationId = parsed.conversationId;
|
|
11550
|
-
const sessionId = parsed.sessionId;
|
|
11551
|
-
const sliceId = parsed.sliceId;
|
|
11552
|
-
const checkpointVersion = parsed.checkpointVersion;
|
|
11553
|
-
const updatedAtMs = parsed.updatedAtMs;
|
|
11554
|
-
const cumulativeDurationMs = toFiniteNonNegativeNumber(
|
|
11555
|
-
parsed.cumulativeDurationMs
|
|
11556
|
-
);
|
|
11557
|
-
const cumulativeUsage = parseAgentTurnUsage(parsed.cumulativeUsage);
|
|
11558
|
-
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
11559
|
-
return void 0;
|
|
11560
|
-
}
|
|
11561
|
-
const legacyPiMessages = Array.isArray(parsed.piMessages) ? parsed.piMessages : [];
|
|
11562
|
-
const messageCount = toFiniteNonNegativeNumber(parsed.messageCount) ?? legacyPiMessages.length;
|
|
11563
|
-
return {
|
|
11564
|
-
legacyPiMessages,
|
|
11565
|
-
record: {
|
|
11566
|
-
checkpointVersion,
|
|
11567
|
-
conversationId,
|
|
11568
|
-
sessionId,
|
|
11569
|
-
sliceId,
|
|
11570
|
-
state: status,
|
|
11571
|
-
updatedAtMs,
|
|
11572
|
-
messageCount,
|
|
11573
|
-
...cumulativeDurationMs !== void 0 ? { cumulativeDurationMs } : {},
|
|
11574
|
-
...cumulativeUsage ? { cumulativeUsage } : {},
|
|
11575
|
-
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
11576
|
-
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
11577
|
-
(value2) => typeof value2 === "string"
|
|
11578
|
-
)
|
|
11579
|
-
} : {},
|
|
11580
|
-
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
11581
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
11582
|
-
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
11583
|
-
}
|
|
11584
|
-
};
|
|
11585
|
-
}
|
|
11586
|
-
function materializePiMessages(legacyPiMessages, messageCount, sessionMessages) {
|
|
11587
|
-
if (messageCount === 0) {
|
|
11588
|
-
return [];
|
|
11589
|
-
}
|
|
11590
|
-
if (sessionMessages) {
|
|
11591
|
-
return sessionMessages;
|
|
11592
|
-
}
|
|
11593
|
-
if (legacyPiMessages.length >= messageCount) {
|
|
11594
|
-
return legacyPiMessages.slice(0, messageCount);
|
|
11595
|
-
}
|
|
11596
|
-
return void 0;
|
|
11597
|
-
}
|
|
11598
|
-
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
11599
|
-
const stateAdapter = getStateAdapter();
|
|
11600
|
-
await stateAdapter.connect();
|
|
11601
|
-
const value = await stateAdapter.get(
|
|
11602
|
-
agentTurnSessionKey(conversationId, sessionId)
|
|
11603
|
-
);
|
|
11604
|
-
const parsed = parseAgentTurnSessionRecord(value);
|
|
11605
|
-
if (!parsed) {
|
|
11606
|
-
return void 0;
|
|
11607
|
-
}
|
|
11608
|
-
const sessionMessages = await loadPiSessionMessages({
|
|
11609
|
-
conversationId,
|
|
11610
|
-
sessionId,
|
|
11611
|
-
messageCount: parsed.record.messageCount
|
|
11612
|
-
});
|
|
11613
|
-
const piMessages = materializePiMessages(
|
|
11614
|
-
parsed.legacyPiMessages,
|
|
11615
|
-
parsed.record.messageCount,
|
|
11616
|
-
sessionMessages
|
|
11617
|
-
);
|
|
11618
|
-
if (!piMessages) {
|
|
11619
|
-
return void 0;
|
|
11620
|
-
}
|
|
11621
|
-
return {
|
|
11622
|
-
...parsed.record,
|
|
11623
|
-
piMessages
|
|
11624
|
-
};
|
|
11625
|
-
}
|
|
11626
|
-
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
11627
|
-
const stateAdapter = getStateAdapter();
|
|
11628
|
-
await stateAdapter.connect();
|
|
11629
|
-
const existingValue = await stateAdapter.get(
|
|
11630
|
-
agentTurnSessionKey(args.conversationId, args.sessionId)
|
|
11631
|
-
);
|
|
11632
|
-
const existingRecord = parseAgentTurnSessionRecord(existingValue);
|
|
11633
|
-
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
11634
|
-
await commitPiSessionMessages({
|
|
11635
|
-
conversationId: args.conversationId,
|
|
11636
|
-
sessionId: args.sessionId,
|
|
11637
|
-
messages: args.piMessages,
|
|
11638
|
-
ttlMs
|
|
11639
|
-
});
|
|
11640
|
-
const storedMessageCount = args.piMessages.length;
|
|
11641
|
-
const checkpoint = {
|
|
11642
|
-
checkpointVersion: (existingRecord?.record.checkpointVersion ?? 0) + 1,
|
|
11643
|
-
conversationId: args.conversationId,
|
|
11644
|
-
sessionId: args.sessionId,
|
|
11645
|
-
sliceId: args.sliceId,
|
|
11646
|
-
state: args.state,
|
|
11647
|
-
updatedAtMs: Date.now(),
|
|
11648
|
-
messageCount: storedMessageCount,
|
|
11649
|
-
...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
|
|
11650
|
-
cumulativeDurationMs: Math.max(
|
|
11651
|
-
0,
|
|
11652
|
-
Math.floor(args.cumulativeDurationMs)
|
|
11653
|
-
)
|
|
11654
|
-
} : {},
|
|
11655
|
-
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
11656
|
-
...Array.isArray(args.loadedSkillNames) ? {
|
|
11657
|
-
loadedSkillNames: args.loadedSkillNames.filter(
|
|
11658
|
-
(value) => typeof value === "string"
|
|
11659
|
-
)
|
|
11660
|
-
} : {},
|
|
11661
|
-
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
11662
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
11663
|
-
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
11664
|
-
};
|
|
11665
|
-
await stateAdapter.set(
|
|
11666
|
-
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
11667
|
-
checkpoint,
|
|
11668
|
-
ttlMs
|
|
11669
|
-
);
|
|
11670
|
-
return {
|
|
11671
|
-
...checkpoint,
|
|
11672
|
-
piMessages: [...args.piMessages]
|
|
11642
|
+
getSandboxId() {
|
|
11643
|
+
return sessionManager.getSandboxId();
|
|
11644
|
+
},
|
|
11645
|
+
getDependencyProfileHash() {
|
|
11646
|
+
return sessionManager.getDependencyProfileHash();
|
|
11647
|
+
},
|
|
11648
|
+
canExecute(toolName) {
|
|
11649
|
+
return SANDBOX_TOOL_NAMES.has(toolName);
|
|
11650
|
+
},
|
|
11651
|
+
async createSandbox() {
|
|
11652
|
+
return await sessionManager.createSandbox();
|
|
11653
|
+
},
|
|
11654
|
+
execute,
|
|
11655
|
+
async dispose() {
|
|
11656
|
+
await sessionManager.dispose();
|
|
11657
|
+
}
|
|
11673
11658
|
};
|
|
11674
11659
|
}
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
11678
|
-
|
|
11679
|
-
);
|
|
11680
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
11681
|
-
return void 0;
|
|
11682
|
-
}
|
|
11683
|
-
return await upsertAgentTurnSessionCheckpoint({
|
|
11684
|
-
conversationId: existing.conversationId,
|
|
11685
|
-
sessionId: existing.sessionId,
|
|
11686
|
-
sliceId: existing.sliceId,
|
|
11687
|
-
state: "superseded",
|
|
11688
|
-
piMessages: existing.piMessages,
|
|
11689
|
-
cumulativeDurationMs: existing.cumulativeDurationMs,
|
|
11690
|
-
cumulativeUsage: existing.cumulativeUsage,
|
|
11691
|
-
loadedSkillNames: existing.loadedSkillNames,
|
|
11692
|
-
resumeReason: existing.resumeReason,
|
|
11693
|
-
resumedFromSliceId: existing.resumedFromSliceId,
|
|
11694
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
11695
|
-
});
|
|
11660
|
+
|
|
11661
|
+
// src/chat/runtime/dev-agent-trace.ts
|
|
11662
|
+
function shouldEmitDevAgentTrace() {
|
|
11663
|
+
return process.env.NODE_ENV === "development";
|
|
11696
11664
|
}
|
|
11697
11665
|
|
|
11698
|
-
// src/chat/services/
|
|
11699
|
-
var
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11666
|
+
// src/chat/services/auth-pause.ts
|
|
11667
|
+
var AuthorizationPauseError = class extends Error {
|
|
11668
|
+
disposition;
|
|
11669
|
+
kind;
|
|
11670
|
+
provider;
|
|
11671
|
+
constructor(kind, provider, disposition) {
|
|
11672
|
+
super(
|
|
11673
|
+
kind === "mcp" ? `MCP authorization started for ${provider}` : `Plugin authorization started for ${provider}`
|
|
11674
|
+
);
|
|
11675
|
+
this.name = kind === "mcp" ? "McpAuthorizationPauseError" : "PluginAuthorizationPauseError";
|
|
11676
|
+
this.disposition = disposition;
|
|
11677
|
+
this.kind = kind;
|
|
11678
|
+
this.provider = provider;
|
|
11704
11679
|
}
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
if (!
|
|
11680
|
+
};
|
|
11681
|
+
|
|
11682
|
+
// src/chat/runtime/report-progress.ts
|
|
11683
|
+
function buildReportedProgressStatus(input) {
|
|
11684
|
+
if (!input || typeof input !== "object") {
|
|
11710
11685
|
return void 0;
|
|
11711
11686
|
}
|
|
11712
|
-
|
|
11687
|
+
const message = input.message;
|
|
11688
|
+
if (typeof message !== "string") {
|
|
11713
11689
|
return void 0;
|
|
11714
11690
|
}
|
|
11715
|
-
|
|
11691
|
+
const text = message.trim();
|
|
11692
|
+
if (!text) {
|
|
11693
|
+
return void 0;
|
|
11694
|
+
}
|
|
11695
|
+
return { text };
|
|
11716
11696
|
}
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
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
|
+
};
|
|
11720
11706
|
}
|
|
11721
|
-
if (
|
|
11722
|
-
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
|
+
};
|
|
11723
11713
|
}
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
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
|
+
};
|
|
11735
11749
|
}
|
|
11750
|
+
return params;
|
|
11736
11751
|
}
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
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;
|
|
11742
11759
|
}
|
|
11743
|
-
|
|
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);
|
|
11744
11776
|
}
|
|
11745
|
-
|
|
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
|
+
]);
|
|
11746
11797
|
}
|
|
11747
11798
|
|
|
11748
11799
|
// src/chat/services/plugin-auth-orchestration.ts
|
|
@@ -12257,7 +12308,11 @@ function buildTurnResult(input) {
|
|
|
12257
12308
|
hasFiles: replyFiles.length > 0
|
|
12258
12309
|
});
|
|
12259
12310
|
const sideEffectOnlySuccess = !primaryText && toolErrorCount === 0 && (reactionPerformed || channelPostPerformed || replyFiles.length > 0);
|
|
12260
|
-
|
|
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) {
|
|
12261
12316
|
logWarn(
|
|
12262
12317
|
"ai_model_response_empty",
|
|
12263
12318
|
{
|
|
@@ -12276,11 +12331,15 @@ function buildTurnResult(input) {
|
|
|
12276
12331
|
"Model returned empty text response"
|
|
12277
12332
|
);
|
|
12278
12333
|
}
|
|
12279
|
-
const lastAssistant = terminalAssistantMessages.at(-1);
|
|
12280
|
-
const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
|
|
12281
|
-
const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
12282
12334
|
const usedPrimaryText = Boolean(primaryText);
|
|
12283
|
-
|
|
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
|
+
}
|
|
12284
12343
|
const suppressReactionOnlyText = reactionPerformed && !channelPostPerformed && replyFiles.length === 0 && Boolean(primaryText) && isReactionOnlyIntent(userInput);
|
|
12285
12344
|
const rawResponseText = suppressReactionOnlyText ? "" : primaryText;
|
|
12286
12345
|
const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
|
|
@@ -12334,6 +12393,27 @@ function buildTurnResult(input) {
|
|
|
12334
12393
|
};
|
|
12335
12394
|
}
|
|
12336
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
|
+
|
|
12337
12417
|
// src/chat/services/turn-thinking-level.ts
|
|
12338
12418
|
import { z } from "zod";
|
|
12339
12419
|
var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
|
|
@@ -12737,7 +12817,7 @@ async function persistAuthPauseCheckpoint(args) {
|
|
|
12737
12817
|
const piMessages = trimTrailingAssistantMessages(
|
|
12738
12818
|
args.messages.length > 0 ? args.messages : latestCheckpoint?.piMessages ?? []
|
|
12739
12819
|
);
|
|
12740
|
-
await upsertAgentTurnSessionCheckpoint({
|
|
12820
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
12741
12821
|
conversationId: args.conversationId,
|
|
12742
12822
|
cumulativeDurationMs: addDurationMs(
|
|
12743
12823
|
latestCheckpoint?.cumulativeDurationMs,
|
|
@@ -12768,7 +12848,7 @@ async function persistAuthPauseCheckpoint(args) {
|
|
|
12768
12848
|
"Failed to persist auth checkpoint before retry"
|
|
12769
12849
|
);
|
|
12770
12850
|
}
|
|
12771
|
-
return
|
|
12851
|
+
return void 0;
|
|
12772
12852
|
}
|
|
12773
12853
|
async function persistTimeoutCheckpoint(args) {
|
|
12774
12854
|
const nextSliceId = args.currentSliceId + 1;
|
|
@@ -12907,6 +12987,10 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
12907
12987
|
}
|
|
12908
12988
|
|
|
12909
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
|
+
}
|
|
12910
12994
|
var startupDiscoveryLogged = false;
|
|
12911
12995
|
var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
|
|
12912
12996
|
function buildOmittedImageAttachmentNotice(count) {
|
|
@@ -13204,11 +13288,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13204
13288
|
const promptConversationContext = context.piMessages && context.piMessages.length > 0 ? void 0 : context.conversationContext;
|
|
13205
13289
|
const userTurnText = buildUserTurnText(
|
|
13206
13290
|
userInput,
|
|
13207
|
-
promptConversationContext
|
|
13208
|
-
{
|
|
13209
|
-
sessionContext: { conversationId: sessionConversationId },
|
|
13210
|
-
turnContext: { traceId: getActiveTraceId() }
|
|
13211
|
-
}
|
|
13291
|
+
promptConversationContext
|
|
13212
13292
|
);
|
|
13213
13293
|
const { routerBlocks, userContentParts } = buildUserTurnInput({
|
|
13214
13294
|
omittedImageAttachmentCount: context.omittedImageAttachmentCount ?? 0,
|
|
@@ -13393,11 +13473,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13393
13473
|
activeMcpCatalogs,
|
|
13394
13474
|
toolGuidance,
|
|
13395
13475
|
runtime: {
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
modelId: botConfig.modelId,
|
|
13399
|
-
slackCapabilities: channelCapabilities,
|
|
13400
|
-
thinkingLevel: thinkingSelection.thinkingLevel
|
|
13476
|
+
conversationId: spanContext.conversationId,
|
|
13477
|
+
traceId: getActiveTraceId()
|
|
13401
13478
|
},
|
|
13402
13479
|
invocation: skillInvocation,
|
|
13403
13480
|
requester: context.requester,
|
|
@@ -13545,67 +13622,96 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13545
13622
|
freshPromptMessage
|
|
13546
13623
|
]);
|
|
13547
13624
|
}
|
|
13548
|
-
const
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
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
|
+
}
|
|
13582
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
|
+
});
|
|
13583
13689
|
if (getPendingAuthPause()) {
|
|
13584
13690
|
timeoutResumeMessages = [...agent.state.messages];
|
|
13585
13691
|
throw getPendingAuthPause();
|
|
13586
13692
|
}
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
if (
|
|
13590
|
-
|
|
13693
|
+
const lastAssistant = outputMessages.at(-1);
|
|
13694
|
+
const retryDelayMs = PROVIDER_RETRY_DELAYS_MS[attempt];
|
|
13695
|
+
if (retryDelayMs === void 0 || !isRetryableProviderError(lastAssistant)) {
|
|
13696
|
+
break;
|
|
13591
13697
|
}
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
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();
|
|
13609
13715
|
}
|
|
13610
13716
|
},
|
|
13611
13717
|
{
|
|
@@ -13682,7 +13788,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13682
13788
|
beforeMessageCount
|
|
13683
13789
|
);
|
|
13684
13790
|
}
|
|
13685
|
-
const
|
|
13791
|
+
const checkpoint = await persistAuthPauseCheckpoint({
|
|
13686
13792
|
conversationId: timeoutResumeConversationId,
|
|
13687
13793
|
sessionId: timeoutResumeSessionId,
|
|
13688
13794
|
currentSliceId: timeoutResumeSliceId,
|
|
@@ -13693,21 +13799,23 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13693
13799
|
errorMessage: error.message,
|
|
13694
13800
|
logContext: checkpointLogContext
|
|
13695
13801
|
});
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
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
|
+
}
|
|
13711
13819
|
}
|
|
13712
13820
|
if (isRetryableTurnError(error)) {
|
|
13713
13821
|
throw error;
|
|
@@ -14923,16 +15031,10 @@ function createResumeReplyContext(args, statusSession) {
|
|
|
14923
15031
|
};
|
|
14924
15032
|
}
|
|
14925
15033
|
async function resumeSlackTurn(args) {
|
|
14926
|
-
if (!args.replyContext?.requester?.userId) {
|
|
14927
|
-
throw new Error("Resumed turn requires replyContext.requester.userId");
|
|
14928
|
-
}
|
|
14929
15034
|
const stateAdapter = getStateAdapter();
|
|
14930
15035
|
await stateAdapter.connect();
|
|
14931
15036
|
const lockKey = args.lockKey ?? getDefaultLockKey(args.channelId, args.threadTs);
|
|
14932
|
-
const lock = await stateAdapter.acquireLock(
|
|
14933
|
-
lockKey,
|
|
14934
|
-
botConfig.turnTimeoutMs + 6e4
|
|
14935
|
-
);
|
|
15037
|
+
const lock = await stateAdapter.acquireLock(lockKey, ACTIVE_LOCK_TTL_MS);
|
|
14936
15038
|
if (!lock) {
|
|
14937
15039
|
throw new ResumeTurnBusyError(lockKey);
|
|
14938
15040
|
}
|
|
@@ -14944,31 +15046,44 @@ async function resumeSlackTurn(args) {
|
|
|
14944
15046
|
let deferredPauseKind;
|
|
14945
15047
|
let deferredPauseHandler;
|
|
14946
15048
|
let deferredFailureHandler;
|
|
15049
|
+
let finalReplyDelivered = false;
|
|
15050
|
+
let postDeliveryCommitError;
|
|
15051
|
+
let runArgs = args;
|
|
14947
15052
|
try {
|
|
14948
|
-
|
|
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) {
|
|
14949
15064
|
processingReaction = await startSlackProcessingReactionForMessage({
|
|
14950
|
-
channelId:
|
|
14951
|
-
timestamp:
|
|
15065
|
+
channelId: runArgs.channelId,
|
|
15066
|
+
timestamp: runArgs.messageTs,
|
|
14952
15067
|
logException,
|
|
14953
|
-
logContext: { ...getResumeLogContext(
|
|
15068
|
+
logContext: { ...getResumeLogContext(runArgs, lockKey) }
|
|
14954
15069
|
});
|
|
14955
15070
|
}
|
|
14956
|
-
if (
|
|
15071
|
+
if (runArgs.initialText) {
|
|
14957
15072
|
await postSlackMessageBestEffort(
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
15073
|
+
runArgs.channelId,
|
|
15074
|
+
runArgs.threadTs,
|
|
15075
|
+
runArgs.initialText
|
|
14961
15076
|
);
|
|
14962
15077
|
}
|
|
14963
15078
|
status.start();
|
|
14964
|
-
const generateReply =
|
|
14965
|
-
const replyContext = createResumeReplyContext(
|
|
15079
|
+
const generateReply = runArgs.generateReply ?? generateAssistantReply;
|
|
15080
|
+
const replyContext = createResumeReplyContext(runArgs, status);
|
|
14966
15081
|
const priorCheckpoint = replyContext.correlation?.conversationId && replyContext.correlation?.turnId ? await getAgentTurnSessionCheckpoint(
|
|
14967
15082
|
replyContext.correlation.conversationId,
|
|
14968
15083
|
replyContext.correlation.turnId
|
|
14969
15084
|
) : void 0;
|
|
14970
|
-
const replyPromise = generateReply(
|
|
14971
|
-
const replyTimeoutMs = resolveReplyTimeoutMs(
|
|
15085
|
+
const replyPromise = generateReply(runArgs.messageText, replyContext);
|
|
15086
|
+
const replyTimeoutMs = resolveReplyTimeoutMs(runArgs.replyTimeoutMs);
|
|
14972
15087
|
let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
|
|
14973
15088
|
replyPromise,
|
|
14974
15089
|
new Promise(
|
|
@@ -14985,11 +15100,11 @@ async function resumeSlackTurn(args) {
|
|
|
14985
15100
|
reply = finalizeFailedTurnReply({
|
|
14986
15101
|
reply,
|
|
14987
15102
|
logException,
|
|
14988
|
-
context: getResumeLogContext(
|
|
15103
|
+
context: getResumeLogContext(runArgs, lockKey)
|
|
14989
15104
|
});
|
|
14990
15105
|
await status.stop();
|
|
14991
15106
|
const footer = buildSlackReplyFooter({
|
|
14992
|
-
conversationId:
|
|
15107
|
+
conversationId: runArgs.replyContext?.correlation?.conversationId ?? lockKey,
|
|
14993
15108
|
durationMs: typeof priorCheckpoint?.cumulativeDurationMs === "number" || typeof reply.diagnostics.durationMs === "number" ? (priorCheckpoint?.cumulativeDurationMs ?? 0) + (reply.diagnostics.durationMs ?? 0) : void 0,
|
|
14994
15109
|
thinkingLevel: reply.diagnostics.thinkingLevel,
|
|
14995
15110
|
usage: addAgentTurnUsage(
|
|
@@ -14998,18 +15113,32 @@ async function resumeSlackTurn(args) {
|
|
|
14998
15113
|
) ?? reply.diagnostics.usage
|
|
14999
15114
|
});
|
|
15000
15115
|
await postSlackApiReplyPosts({
|
|
15001
|
-
channelId:
|
|
15002
|
-
threadTs:
|
|
15116
|
+
channelId: runArgs.channelId,
|
|
15117
|
+
threadTs: runArgs.threadTs,
|
|
15003
15118
|
posts: planSlackReplyPosts({ reply }),
|
|
15004
15119
|
fileUploadFailureMode: "best_effort",
|
|
15005
15120
|
footer
|
|
15006
15121
|
});
|
|
15007
|
-
|
|
15122
|
+
finalReplyDelivered = true;
|
|
15123
|
+
await runArgs.onSuccess?.(reply);
|
|
15008
15124
|
} catch (error) {
|
|
15009
15125
|
await status.stop();
|
|
15010
|
-
const onAuthPause =
|
|
15011
|
-
const onTimeoutPause =
|
|
15012
|
-
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) {
|
|
15013
15142
|
deferredPauseKind = "auth";
|
|
15014
15143
|
deferredPauseHandler = async () => {
|
|
15015
15144
|
await onAuthPause(error);
|
|
@@ -15026,7 +15155,7 @@ async function resumeSlackTurn(args) {
|
|
|
15026
15155
|
error,
|
|
15027
15156
|
eventName: "slack_resume_turn_failed",
|
|
15028
15157
|
lockKey,
|
|
15029
|
-
resumeArgs:
|
|
15158
|
+
resumeArgs: runArgs
|
|
15030
15159
|
});
|
|
15031
15160
|
};
|
|
15032
15161
|
}
|
|
@@ -15034,20 +15163,30 @@ async function resumeSlackTurn(args) {
|
|
|
15034
15163
|
await processingReaction?.stop();
|
|
15035
15164
|
await stateAdapter.releaseLock(lock);
|
|
15036
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
|
+
}
|
|
15037
15176
|
if (deferredPauseHandler) {
|
|
15038
15177
|
try {
|
|
15039
15178
|
await deferredPauseHandler();
|
|
15040
15179
|
if (deferredPauseKind === "auth") {
|
|
15041
15180
|
await postSlackMessageBestEffort(
|
|
15042
|
-
|
|
15043
|
-
|
|
15181
|
+
runArgs.channelId,
|
|
15182
|
+
runArgs.threadTs,
|
|
15044
15183
|
buildAuthPauseResponse()
|
|
15045
15184
|
);
|
|
15046
15185
|
}
|
|
15047
15186
|
if (deferredPauseKind === "timeout") {
|
|
15048
15187
|
await postTurnContinuationNoticeBestEffort({
|
|
15049
15188
|
lockKey,
|
|
15050
|
-
resumeArgs:
|
|
15189
|
+
resumeArgs: runArgs
|
|
15051
15190
|
});
|
|
15052
15191
|
}
|
|
15053
15192
|
return;
|
|
@@ -15057,7 +15196,7 @@ async function resumeSlackTurn(args) {
|
|
|
15057
15196
|
error: pauseError,
|
|
15058
15197
|
eventName: "slack_resume_pause_handler_failed",
|
|
15059
15198
|
lockKey,
|
|
15060
|
-
resumeArgs:
|
|
15199
|
+
resumeArgs: runArgs
|
|
15061
15200
|
});
|
|
15062
15201
|
return;
|
|
15063
15202
|
}
|
|
@@ -15080,6 +15219,8 @@ async function resumeAuthorizedRequest(args) {
|
|
|
15080
15219
|
onFailure: args.onFailure,
|
|
15081
15220
|
onAuthPause: args.onAuthPause,
|
|
15082
15221
|
onTimeoutPause: args.onTimeoutPause,
|
|
15222
|
+
onPostDeliveryCommitFailure: args.onPostDeliveryCommitFailure,
|
|
15223
|
+
beforeStart: args.beforeStart,
|
|
15083
15224
|
replyTimeoutMs: args.replyTimeoutMs
|
|
15084
15225
|
});
|
|
15085
15226
|
}
|
|
@@ -15114,6 +15255,7 @@ async function persistAuthPauseTurnState(args) {
|
|
|
15114
15255
|
// src/chat/services/timeout-resume.ts
|
|
15115
15256
|
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
15116
15257
|
var TURN_TIMEOUT_RESUME_PATH = "/api/internal/turn-resume";
|
|
15258
|
+
var TURN_TIMEOUT_RESUME_HMAC_CONTEXT = "junior.turn_timeout_resume.v1";
|
|
15117
15259
|
var TURN_TIMEOUT_RESUME_SIGNATURE_VERSION = "v1";
|
|
15118
15260
|
var TURN_TIMEOUT_RESUME_MAX_SKEW_MS = 5 * 60 * 1e3;
|
|
15119
15261
|
var TURN_TIMEOUT_RESUME_TIMESTAMP_HEADER = "x-junior-resume-timestamp";
|
|
@@ -15137,14 +15279,10 @@ async function getAwaitingTurnContinuationRequest(args) {
|
|
|
15137
15279
|
};
|
|
15138
15280
|
}
|
|
15139
15281
|
function getTurnTimeoutResumeSecret() {
|
|
15140
|
-
|
|
15141
|
-
if (explicit) {
|
|
15142
|
-
return explicit;
|
|
15143
|
-
}
|
|
15144
|
-
return getSlackSigningSecret();
|
|
15282
|
+
return process.env.JUNIOR_SECRET?.trim() || void 0;
|
|
15145
15283
|
}
|
|
15146
15284
|
function buildSignedPayload(timestamp, body) {
|
|
15147
|
-
return `${timestamp}:${body}`;
|
|
15285
|
+
return `${TURN_TIMEOUT_RESUME_HMAC_CONTEXT}:${timestamp}:${body}`;
|
|
15148
15286
|
}
|
|
15149
15287
|
function signTurnTimeoutResumeBody(secret, timestamp, body) {
|
|
15150
15288
|
const digest = createHmac2("sha256", secret).update(buildSignedPayload(timestamp, body)).digest("hex");
|
|
@@ -15182,7 +15320,7 @@ async function scheduleTurnTimeoutResume(request) {
|
|
|
15182
15320
|
const secret = getTurnTimeoutResumeSecret();
|
|
15183
15321
|
if (!secret) {
|
|
15184
15322
|
throw new Error(
|
|
15185
|
-
"Cannot determine timeout resume secret (set
|
|
15323
|
+
"Cannot determine timeout resume secret (set JUNIOR_SECRET)"
|
|
15186
15324
|
);
|
|
15187
15325
|
}
|
|
15188
15326
|
const body = JSON.stringify(request);
|
|
@@ -15280,54 +15418,43 @@ function htmlResponse(kind) {
|
|
|
15280
15418
|
const page = CALLBACK_PAGES[kind];
|
|
15281
15419
|
return htmlCallbackResponse(page.title, page.message, page.status);
|
|
15282
15420
|
}
|
|
15283
|
-
async function buildResumeConversationContext(channelId, threadTs, sessionId) {
|
|
15284
|
-
const threadId = `slack:${channelId}:${threadTs}`;
|
|
15285
|
-
const conversation = coerceThreadConversationState(
|
|
15286
|
-
await getPersistedThreadState(threadId)
|
|
15287
|
-
);
|
|
15288
|
-
const userMessageId = getTurnUserMessageId(conversation, sessionId);
|
|
15289
|
-
return buildConversationContext(conversation, {
|
|
15290
|
-
excludeMessageId: userMessageId
|
|
15291
|
-
});
|
|
15292
|
-
}
|
|
15293
15421
|
async function persistCompletedReplyState(channelId, threadTs, sessionId, reply) {
|
|
15294
15422
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
15295
15423
|
const currentState = await getPersistedThreadState(threadId);
|
|
15296
15424
|
const conversation = coerceThreadConversationState(currentState);
|
|
15297
15425
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15298
|
-
const nextArtifacts = reply.artifactStatePatch ? mergeArtifactsState(artifacts, reply.artifactStatePatch) : void 0;
|
|
15299
15426
|
const userMessage = getTurnUserMessage(conversation, sessionId);
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
replied: true,
|
|
15303
|
-
skippedReason: void 0
|
|
15304
|
-
});
|
|
15305
|
-
upsertConversationMessage(conversation, {
|
|
15306
|
-
id: generateConversationId("assistant"),
|
|
15307
|
-
role: "assistant",
|
|
15308
|
-
text: normalizeConversationText(reply.text) || "[empty response]",
|
|
15309
|
-
createdAtMs: Date.now(),
|
|
15310
|
-
author: {
|
|
15311
|
-
userName: botConfig.userName,
|
|
15312
|
-
isBot: true
|
|
15313
|
-
},
|
|
15314
|
-
meta: {
|
|
15315
|
-
replied: true
|
|
15316
|
-
}
|
|
15317
|
-
});
|
|
15318
|
-
markTurnCompleted({
|
|
15427
|
+
const statePatch = buildDeliveredTurnStatePatch({
|
|
15428
|
+
artifacts,
|
|
15319
15429
|
conversation,
|
|
15320
|
-
|
|
15430
|
+
reply,
|
|
15321
15431
|
sessionId,
|
|
15322
|
-
|
|
15432
|
+
userMessageId: userMessage?.id
|
|
15323
15433
|
});
|
|
15324
15434
|
await persistThreadStateById(threadId, {
|
|
15325
|
-
|
|
15326
|
-
conversation,
|
|
15327
|
-
sandboxId: reply.sandboxId,
|
|
15328
|
-
sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash
|
|
15435
|
+
...statePatch
|
|
15329
15436
|
});
|
|
15330
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
|
+
}
|
|
15331
15458
|
async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
15332
15459
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
15333
15460
|
const currentState = await getPersistedThreadState(threadId);
|
|
@@ -15341,6 +15468,11 @@ async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
|
15341
15468
|
markConversationMessage,
|
|
15342
15469
|
updateConversationStats
|
|
15343
15470
|
});
|
|
15471
|
+
await failCheckpointBestEffort({
|
|
15472
|
+
conversationId: threadId,
|
|
15473
|
+
sessionId,
|
|
15474
|
+
errorMessage: "OAuth-resumed MCP turn failed"
|
|
15475
|
+
});
|
|
15344
15476
|
await persistThreadStateById(threadId, {
|
|
15345
15477
|
conversation
|
|
15346
15478
|
});
|
|
@@ -15353,7 +15485,6 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
15353
15485
|
const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
|
|
15354
15486
|
const currentState = await getPersistedThreadState(threadId);
|
|
15355
15487
|
const conversation = coerceThreadConversationState(currentState);
|
|
15356
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15357
15488
|
const pendingAuth = getConversationPendingAuth({
|
|
15358
15489
|
conversation,
|
|
15359
15490
|
kind: "mcp",
|
|
@@ -15379,132 +15510,174 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
15379
15510
|
if (!userMessage) {
|
|
15380
15511
|
return;
|
|
15381
15512
|
}
|
|
15382
|
-
const channelConfiguration = getChannelConfigurationServiceById(
|
|
15383
|
-
authSession.channelId
|
|
15384
|
-
);
|
|
15385
|
-
const conversationContext = await buildResumeConversationContext(
|
|
15386
|
-
authSession.channelId,
|
|
15387
|
-
authSession.threadTs,
|
|
15388
|
-
resolvedSessionId
|
|
15389
|
-
);
|
|
15390
15513
|
await resumeAuthorizedRequest({
|
|
15391
15514
|
messageText: userMessage.text,
|
|
15392
15515
|
channelId: authSession.channelId,
|
|
15393
15516
|
threadTs: authSession.threadTs,
|
|
15394
15517
|
messageTs: getTurnUserSlackMessageTs(userMessage),
|
|
15395
|
-
lockKey:
|
|
15518
|
+
lockKey: threadId,
|
|
15396
15519
|
connectedText: "",
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15404
|
-
|
|
15405
|
-
turnId: resolvedSessionId,
|
|
15406
|
-
channelId: authSession.channelId,
|
|
15407
|
-
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,
|
|
15408
15528
|
requesterId: authSession.userId
|
|
15409
|
-
}
|
|
15410
|
-
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
piMessages: conversation.piMessages,
|
|
15414
|
-
configuration: authSession.configuration,
|
|
15415
|
-
pendingAuth,
|
|
15416
|
-
channelConfiguration,
|
|
15417
|
-
sandbox: getPersistedSandboxState(currentState),
|
|
15418
|
-
onAuthPending: async (nextPendingAuth) => {
|
|
15419
|
-
await applyPendingAuthUpdate({
|
|
15420
|
-
conversation,
|
|
15421
|
-
conversationId: authSession.conversationId,
|
|
15422
|
-
nextPendingAuth
|
|
15423
|
-
});
|
|
15424
|
-
await persistThreadStateById(threadId, { conversation });
|
|
15425
|
-
},
|
|
15426
|
-
...getTurnUserReplyAttachmentContext(userMessage)
|
|
15427
|
-
},
|
|
15428
|
-
onSuccess: async (reply) => {
|
|
15429
|
-
try {
|
|
15430
|
-
await persistCompletedReplyState(
|
|
15431
|
-
authSession.channelId,
|
|
15432
|
-
authSession.threadTs,
|
|
15433
|
-
resolvedSessionId,
|
|
15434
|
-
reply
|
|
15435
|
-
);
|
|
15436
|
-
} catch (persistError) {
|
|
15437
|
-
logException(
|
|
15438
|
-
persistError,
|
|
15439
|
-
"mcp_oauth_callback_resume_persist_failed",
|
|
15440
|
-
{},
|
|
15441
|
-
{ "app.credential.provider": provider },
|
|
15442
|
-
"Failed to persist resumed MCP turn state"
|
|
15443
|
-
);
|
|
15529
|
+
});
|
|
15530
|
+
const lockedSessionId = lockedPendingAuth?.sessionId ?? authSession.sessionId;
|
|
15531
|
+
if (lockedSessionId !== resolvedSessionId) {
|
|
15532
|
+
return false;
|
|
15444
15533
|
}
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
);
|
|
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;
|
|
15461
15549
|
}
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
sessionId: resolvedSessionId,
|
|
15466
|
-
threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}`
|
|
15467
|
-
});
|
|
15468
|
-
logWarn(
|
|
15469
|
-
"mcp_oauth_callback_resume_reparked_for_auth",
|
|
15470
|
-
{},
|
|
15471
|
-
{
|
|
15472
|
-
"app.credential.provider": provider,
|
|
15473
|
-
...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
|
|
15474
|
-
},
|
|
15475
|
-
"Resumed MCP turn requested another authorization flow"
|
|
15550
|
+
const lockedUserMessage = getTurnUserMessage(
|
|
15551
|
+
lockedConversation,
|
|
15552
|
+
lockedSessionId
|
|
15476
15553
|
);
|
|
15477
|
-
|
|
15478
|
-
|
|
15479
|
-
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
15480
|
-
throw error;
|
|
15481
|
-
}
|
|
15482
|
-
const checkpointVersion = error.metadata?.checkpointVersion;
|
|
15483
|
-
const nextSliceId = error.metadata?.sliceId;
|
|
15484
|
-
if (typeof checkpointVersion !== "number") {
|
|
15485
|
-
throw new Error(
|
|
15486
|
-
"Timed-out MCP resume did not include a checkpoint version"
|
|
15487
|
-
);
|
|
15554
|
+
if (!lockedUserMessage) {
|
|
15555
|
+
return false;
|
|
15488
15556
|
}
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
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
|
|
15496
15574
|
},
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
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
|
+
};
|
|
15508
15681
|
}
|
|
15509
15682
|
});
|
|
15510
15683
|
}
|
|
@@ -15703,52 +15876,42 @@ async function publishAppHomeView(slackClient, userId, userTokenStore) {
|
|
|
15703
15876
|
function htmlErrorResponse(title, message, status) {
|
|
15704
15877
|
return htmlCallbackResponse(escapeXml(title), escapeXml(message), status);
|
|
15705
15878
|
}
|
|
15706
|
-
async function buildCheckpointConversationContext(conversationId, sessionId) {
|
|
15707
|
-
const conversation = coerceThreadConversationState(
|
|
15708
|
-
await getPersistedThreadState(conversationId)
|
|
15709
|
-
);
|
|
15710
|
-
const userMessage = getTurnUserMessage(conversation, sessionId);
|
|
15711
|
-
return buildConversationContext(conversation, {
|
|
15712
|
-
excludeMessageId: userMessage?.id
|
|
15713
|
-
});
|
|
15714
|
-
}
|
|
15715
15879
|
async function persistCompletedOAuthReplyState(args) {
|
|
15716
15880
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
15717
15881
|
const conversation = coerceThreadConversationState(currentState);
|
|
15718
15882
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15719
|
-
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
15720
15883
|
const userMessage = getTurnUserMessage(conversation, args.sessionId);
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
replied: true,
|
|
15724
|
-
skippedReason: void 0
|
|
15725
|
-
});
|
|
15726
|
-
upsertConversationMessage(conversation, {
|
|
15727
|
-
id: generateConversationId("assistant"),
|
|
15728
|
-
role: "assistant",
|
|
15729
|
-
text: normalizeConversationText(args.reply.text) || "[empty response]",
|
|
15730
|
-
createdAtMs: Date.now(),
|
|
15731
|
-
author: {
|
|
15732
|
-
userName: botConfig.userName,
|
|
15733
|
-
isBot: true
|
|
15734
|
-
},
|
|
15735
|
-
meta: {
|
|
15736
|
-
replied: true
|
|
15737
|
-
}
|
|
15738
|
-
});
|
|
15739
|
-
markTurnCompleted({
|
|
15884
|
+
const statePatch = buildDeliveredTurnStatePatch({
|
|
15885
|
+
artifacts,
|
|
15740
15886
|
conversation,
|
|
15741
|
-
|
|
15887
|
+
reply: args.reply,
|
|
15742
15888
|
sessionId: args.sessionId,
|
|
15743
|
-
|
|
15889
|
+
userMessageId: userMessage?.id
|
|
15744
15890
|
});
|
|
15745
15891
|
await persistThreadStateById(args.conversationId, {
|
|
15746
|
-
|
|
15747
|
-
conversation,
|
|
15748
|
-
sandboxId: args.reply.sandboxId,
|
|
15749
|
-
sandboxDependencyProfileHash: args.reply.sandboxDependencyProfileHash
|
|
15892
|
+
...statePatch
|
|
15750
15893
|
});
|
|
15751
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
|
+
}
|
|
15752
15915
|
async function persistFailedOAuthReplyState(args) {
|
|
15753
15916
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
15754
15917
|
const conversation = coerceThreadConversationState(currentState);
|
|
@@ -15761,6 +15924,11 @@ async function persistFailedOAuthReplyState(args) {
|
|
|
15761
15924
|
markConversationMessage,
|
|
15762
15925
|
updateConversationStats
|
|
15763
15926
|
});
|
|
15927
|
+
await failCheckpointBestEffort2({
|
|
15928
|
+
conversationId: args.conversationId,
|
|
15929
|
+
sessionId: args.sessionId,
|
|
15930
|
+
errorMessage: "OAuth-resumed turn failed"
|
|
15931
|
+
});
|
|
15764
15932
|
await persistThreadStateById(args.conversationId, {
|
|
15765
15933
|
conversation
|
|
15766
15934
|
});
|
|
@@ -15786,7 +15954,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
15786
15954
|
stored.resumeConversationId
|
|
15787
15955
|
);
|
|
15788
15956
|
const conversation = coerceThreadConversationState(currentState);
|
|
15789
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15790
15957
|
const pendingAuth = getConversationPendingAuth({
|
|
15791
15958
|
conversation,
|
|
15792
15959
|
kind: "plugin",
|
|
@@ -15817,104 +15984,165 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
15817
15984
|
}
|
|
15818
15985
|
}
|
|
15819
15986
|
if (!userMessage?.author?.userId || !resolvedSessionId) {
|
|
15820
|
-
return false;
|
|
15821
|
-
}
|
|
15822
|
-
|
|
15823
|
-
stored.
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
stored.
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
messageTs: getTurnUserSlackMessageTs(userMessage),
|
|
15834
|
-
lockKey: stored.resumeConversationId,
|
|
15835
|
-
initialText: "",
|
|
15836
|
-
replyContext: {
|
|
15837
|
-
requester: {
|
|
15838
|
-
userId: userMessage.author.userId,
|
|
15839
|
-
userName: userMessage.author.userName,
|
|
15840
|
-
fullName: userMessage.author.fullName
|
|
15841
|
-
},
|
|
15842
|
-
correlation: {
|
|
15843
|
-
conversationId: stored.resumeConversationId,
|
|
15844
|
-
turnId: resolvedSessionId,
|
|
15845
|
-
channelId: stored.channelId,
|
|
15846
|
-
threadTs: stored.threadTs,
|
|
15847
|
-
requesterId: userMessage.author.userId
|
|
15848
|
-
},
|
|
15849
|
-
toolChannelId: artifacts.assistantContextChannelId ?? stored.channelId,
|
|
15850
|
-
artifactState: artifacts,
|
|
15851
|
-
pendingAuth,
|
|
15852
|
-
conversationContext,
|
|
15853
|
-
channelConfiguration,
|
|
15854
|
-
piMessages: conversation.piMessages,
|
|
15855
|
-
sandbox: getPersistedSandboxState(currentState),
|
|
15856
|
-
onAuthPending: async (nextPendingAuth) => {
|
|
15857
|
-
await applyPendingAuthUpdate({
|
|
15858
|
-
conversation,
|
|
15859
|
-
conversationId: stored.resumeConversationId,
|
|
15860
|
-
nextPendingAuth
|
|
15861
|
-
});
|
|
15862
|
-
await persistThreadStateById(stored.resumeConversationId, {
|
|
15863
|
-
conversation
|
|
15864
|
-
});
|
|
15865
|
-
},
|
|
15866
|
-
...getTurnUserReplyAttachmentContext(userMessage)
|
|
15867
|
-
},
|
|
15868
|
-
onSuccess: async (reply) => {
|
|
15869
|
-
logInfo(
|
|
15870
|
-
"oauth_callback_resume_complete",
|
|
15871
|
-
{},
|
|
15872
|
-
{
|
|
15873
|
-
"app.credential.provider": stored.provider,
|
|
15874
|
-
"app.ai.outcome": reply.diagnostics.outcome,
|
|
15875
|
-
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
15876
|
-
},
|
|
15877
|
-
"OAuth callback auto-resumed checkpoint finished replying"
|
|
15987
|
+
return false;
|
|
15988
|
+
}
|
|
15989
|
+
await resumeSlackTurn({
|
|
15990
|
+
messageText: stored.pendingMessage ?? userMessage.text,
|
|
15991
|
+
channelId: stored.channelId,
|
|
15992
|
+
threadTs: stored.threadTs,
|
|
15993
|
+
messageTs: getTurnUserSlackMessageTs(userMessage),
|
|
15994
|
+
lockKey: stored.resumeConversationId,
|
|
15995
|
+
initialText: "",
|
|
15996
|
+
beforeStart: async () => {
|
|
15997
|
+
const lockedCheckpoint = await getAgentTurnSessionCheckpoint(
|
|
15998
|
+
stored.resumeConversationId,
|
|
15999
|
+
stored.resumeSessionId
|
|
15878
16000
|
);
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
await persistAuthPauseTurnState({
|
|
15893
|
-
sessionId: resolvedSessionId,
|
|
15894
|
-
threadStateId: stored.resumeConversationId
|
|
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
|
|
15895
16014
|
});
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
throw error;
|
|
16015
|
+
const lockedSessionId = lockedPendingAuth?.sessionId ?? stored.resumeSessionId;
|
|
16016
|
+
if (lockedSessionId !== resolvedSessionId) {
|
|
16017
|
+
return false;
|
|
15900
16018
|
}
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
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;
|
|
15907
16034
|
}
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
16035
|
+
const lockedUserMessage = getTurnUserMessage(
|
|
16036
|
+
lockedConversation,
|
|
16037
|
+
lockedSessionId
|
|
16038
|
+
);
|
|
16039
|
+
if (!lockedUserMessage?.author?.userId) {
|
|
16040
|
+
return false;
|
|
15912
16041
|
}
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
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
|
+
};
|
|
15918
16146
|
}
|
|
15919
16147
|
});
|
|
15920
16148
|
return true;
|
|
@@ -16670,7 +16898,7 @@ function isSandboxEgressRequest(request) {
|
|
|
16670
16898
|
|
|
16671
16899
|
// src/handlers/turn-resume.ts
|
|
16672
16900
|
var TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
16673
|
-
function
|
|
16901
|
+
function sleep4(ms) {
|
|
16674
16902
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16675
16903
|
}
|
|
16676
16904
|
async function persistCompletedReplyState2(args) {
|
|
@@ -16679,42 +16907,42 @@ async function persistCompletedReplyState2(args) {
|
|
|
16679
16907
|
);
|
|
16680
16908
|
const conversation = coerceThreadConversationState(currentState);
|
|
16681
16909
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
16682
|
-
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
16683
16910
|
const userMessage = getTurnUserMessage(
|
|
16684
16911
|
conversation,
|
|
16685
16912
|
args.checkpoint.sessionId
|
|
16686
16913
|
);
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
replied: true,
|
|
16690
|
-
skippedReason: void 0
|
|
16691
|
-
});
|
|
16692
|
-
upsertConversationMessage(conversation, {
|
|
16693
|
-
id: generateConversationId("assistant"),
|
|
16694
|
-
role: "assistant",
|
|
16695
|
-
text: normalizeConversationText(args.reply.text) || "[empty response]",
|
|
16696
|
-
createdAtMs: Date.now(),
|
|
16697
|
-
author: {
|
|
16698
|
-
userName: botConfig.userName,
|
|
16699
|
-
isBot: true
|
|
16700
|
-
},
|
|
16701
|
-
meta: {
|
|
16702
|
-
replied: true
|
|
16703
|
-
}
|
|
16704
|
-
});
|
|
16705
|
-
markTurnCompleted({
|
|
16914
|
+
const statePatch = buildDeliveredTurnStatePatch({
|
|
16915
|
+
artifacts,
|
|
16706
16916
|
conversation,
|
|
16707
|
-
|
|
16917
|
+
reply: args.reply,
|
|
16708
16918
|
sessionId: args.checkpoint.sessionId,
|
|
16709
|
-
|
|
16919
|
+
userMessageId: userMessage?.id
|
|
16710
16920
|
});
|
|
16711
16921
|
await persistThreadStateById(args.checkpoint.conversationId, {
|
|
16712
|
-
|
|
16713
|
-
conversation,
|
|
16714
|
-
sandboxId: args.reply.sandboxId,
|
|
16715
|
-
sandboxDependencyProfileHash: args.reply.sandboxDependencyProfileHash
|
|
16922
|
+
...statePatch
|
|
16716
16923
|
});
|
|
16717
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
|
+
}
|
|
16718
16946
|
async function persistFailedReplyState2(checkpoint) {
|
|
16719
16947
|
const currentState = await getPersistedThreadState(checkpoint.conversationId);
|
|
16720
16948
|
const conversation = coerceThreadConversationState(currentState);
|
|
@@ -16727,145 +16955,152 @@ async function persistFailedReplyState2(checkpoint) {
|
|
|
16727
16955
|
markConversationMessage,
|
|
16728
16956
|
updateConversationStats
|
|
16729
16957
|
});
|
|
16958
|
+
await failCheckpointBestEffort3({
|
|
16959
|
+
checkpoint,
|
|
16960
|
+
errorMessage: "Timed-out turn failed while resuming"
|
|
16961
|
+
});
|
|
16730
16962
|
await persistThreadStateById(checkpoint.conversationId, {
|
|
16731
16963
|
conversation
|
|
16732
16964
|
});
|
|
16733
16965
|
}
|
|
16734
16966
|
async function resumeTimedOutTurn(payload) {
|
|
16735
|
-
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
16736
|
-
payload.conversationId,
|
|
16737
|
-
payload.sessionId
|
|
16738
|
-
);
|
|
16739
|
-
if (!checkpoint || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "timeout" || checkpoint.checkpointVersion !== payload.expectedCheckpointVersion) {
|
|
16740
|
-
return;
|
|
16741
|
-
}
|
|
16742
16967
|
const thread = parseSlackThreadId(payload.conversationId);
|
|
16743
16968
|
if (!thread) {
|
|
16744
16969
|
throw new Error(
|
|
16745
16970
|
`Timeout resume requires a Slack thread conversation id, got "${payload.conversationId}"`
|
|
16746
16971
|
);
|
|
16747
16972
|
}
|
|
16748
|
-
const currentState = await getPersistedThreadState(payload.conversationId);
|
|
16749
|
-
const conversation = coerceThreadConversationState(currentState);
|
|
16750
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
16751
|
-
const userMessage = getTurnUserMessage(conversation, payload.sessionId);
|
|
16752
|
-
if (!userMessage?.author?.userId) {
|
|
16753
|
-
throw new Error(
|
|
16754
|
-
`Unable to locate the persisted user message for timeout resume session "${payload.sessionId}"`
|
|
16755
|
-
);
|
|
16756
|
-
}
|
|
16757
|
-
if (conversation.processing.activeTurnId !== payload.sessionId) {
|
|
16758
|
-
return;
|
|
16759
|
-
}
|
|
16760
|
-
const channelConfiguration = getChannelConfigurationServiceById(
|
|
16761
|
-
thread.channelId
|
|
16762
|
-
);
|
|
16763
|
-
const conversationContext = buildConversationContext(conversation, {
|
|
16764
|
-
excludeMessageId: userMessage.id
|
|
16765
|
-
});
|
|
16766
|
-
const sandbox = getPersistedSandboxState(currentState);
|
|
16767
16973
|
await resumeSlackTurn({
|
|
16768
|
-
messageText:
|
|
16974
|
+
messageText: "",
|
|
16769
16975
|
channelId: thread.channelId,
|
|
16770
16976
|
threadTs: thread.threadTs,
|
|
16771
16977
|
lockKey: payload.conversationId,
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
fullName: userMessage.author.fullName
|
|
16777
|
-
},
|
|
16778
|
-
correlation: {
|
|
16779
|
-
conversationId: payload.conversationId,
|
|
16780
|
-
turnId: payload.sessionId,
|
|
16781
|
-
channelId: thread.channelId,
|
|
16782
|
-
threadTs: thread.threadTs,
|
|
16783
|
-
requesterId: userMessage.author.userId
|
|
16784
|
-
},
|
|
16785
|
-
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
16786
|
-
artifactState: artifacts,
|
|
16787
|
-
pendingAuth: conversation.processing.pendingAuth,
|
|
16788
|
-
conversationContext,
|
|
16789
|
-
channelConfiguration,
|
|
16790
|
-
piMessages: conversation.piMessages,
|
|
16791
|
-
sandbox,
|
|
16792
|
-
onAuthPending: async (nextPendingAuth) => {
|
|
16793
|
-
await applyPendingAuthUpdate({
|
|
16794
|
-
conversation,
|
|
16795
|
-
conversationId: payload.conversationId,
|
|
16796
|
-
nextPendingAuth
|
|
16797
|
-
});
|
|
16798
|
-
await persistThreadStateById(payload.conversationId, {
|
|
16799
|
-
conversation
|
|
16800
|
-
});
|
|
16801
|
-
},
|
|
16802
|
-
...getTurnUserReplyAttachmentContext(userMessage)
|
|
16803
|
-
},
|
|
16804
|
-
onSuccess: async (reply) => {
|
|
16805
|
-
try {
|
|
16806
|
-
await persistCompletedReplyState2({ checkpoint, reply });
|
|
16807
|
-
} catch (persistError) {
|
|
16808
|
-
logException(
|
|
16809
|
-
persistError,
|
|
16810
|
-
"timeout_resume_complete_persist_failed",
|
|
16811
|
-
{},
|
|
16812
|
-
{
|
|
16813
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
16814
|
-
"app.ai.session_id": payload.sessionId
|
|
16815
|
-
},
|
|
16816
|
-
"Failed to persist completed timeout-resume state after reply delivery"
|
|
16817
|
-
);
|
|
16818
|
-
}
|
|
16819
|
-
},
|
|
16820
|
-
onFailure: async () => {
|
|
16821
|
-
await persistFailedReplyState2(checkpoint);
|
|
16822
|
-
},
|
|
16823
|
-
onAuthPause: async () => {
|
|
16824
|
-
await persistAuthPauseTurnState({
|
|
16825
|
-
sessionId: payload.sessionId,
|
|
16826
|
-
threadStateId: payload.conversationId
|
|
16827
|
-
});
|
|
16828
|
-
logWarn(
|
|
16829
|
-
"timeout_resume_reparked_for_auth",
|
|
16830
|
-
{},
|
|
16831
|
-
{
|
|
16832
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
16833
|
-
"app.ai.session_id": payload.sessionId
|
|
16834
|
-
},
|
|
16835
|
-
"Resumed timed-out turn parked for auth"
|
|
16978
|
+
beforeStart: async () => {
|
|
16979
|
+
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
16980
|
+
payload.conversationId,
|
|
16981
|
+
payload.sessionId
|
|
16836
16982
|
);
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
16840
|
-
throw error;
|
|
16983
|
+
if (!checkpoint || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "timeout" || checkpoint.checkpointVersion !== payload.expectedCheckpointVersion) {
|
|
16984
|
+
return false;
|
|
16841
16985
|
}
|
|
16842
|
-
const
|
|
16843
|
-
|
|
16844
|
-
|
|
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) {
|
|
16845
16993
|
throw new Error(
|
|
16846
|
-
|
|
16994
|
+
`Unable to locate the persisted user message for timeout resume session "${payload.sessionId}"`
|
|
16847
16995
|
);
|
|
16848
16996
|
}
|
|
16849
|
-
if (
|
|
16850
|
-
|
|
16851
|
-
"timeout_resume_slice_limit_reached",
|
|
16852
|
-
{},
|
|
16853
|
-
{
|
|
16854
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
16855
|
-
"app.ai.session_id": payload.sessionId,
|
|
16856
|
-
...typeof nextSliceId === "number" ? { "app.ai.resume_slice_id": nextSliceId } : {}
|
|
16857
|
-
},
|
|
16858
|
-
"Skipped automatic timeout resume because the turn exceeded the slice limit"
|
|
16859
|
-
);
|
|
16860
|
-
throw new Error(
|
|
16861
|
-
"Timed-out turn exceeded the automatic resume slice limit"
|
|
16862
|
-
);
|
|
16997
|
+
if (conversation.processing.activeTurnId !== payload.sessionId) {
|
|
16998
|
+
return false;
|
|
16863
16999
|
}
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
|
|
16867
|
-
|
|
17000
|
+
const channelConfiguration = getChannelConfigurationServiceById(
|
|
17001
|
+
thread.channelId
|
|
17002
|
+
);
|
|
17003
|
+
const conversationContext = buildConversationContext(conversation, {
|
|
17004
|
+
excludeMessageId: userMessage.id
|
|
16868
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
|
+
};
|
|
16869
17104
|
}
|
|
16870
17105
|
});
|
|
16871
17106
|
}
|
|
@@ -16890,8 +17125,9 @@ async function resumeTimedOutTurnWithLockRetry(payload) {
|
|
|
16890
17125
|
"app.ai.session_id": payload.sessionId,
|
|
16891
17126
|
"app.ai.resume_lock_retry_count": attempt
|
|
16892
17127
|
},
|
|
16893
|
-
"
|
|
17128
|
+
"Rescheduling timeout resume because another turn still owns the thread lock"
|
|
16894
17129
|
);
|
|
17130
|
+
await scheduleTurnTimeoutResume(payload);
|
|
16895
17131
|
return;
|
|
16896
17132
|
}
|
|
16897
17133
|
logWarn(
|
|
@@ -16905,7 +17141,7 @@ async function resumeTimedOutTurnWithLockRetry(payload) {
|
|
|
16905
17141
|
},
|
|
16906
17142
|
"Timeout resume lock was busy; retrying"
|
|
16907
17143
|
);
|
|
16908
|
-
await
|
|
17144
|
+
await sleep4(delayMs);
|
|
16909
17145
|
}
|
|
16910
17146
|
}
|
|
16911
17147
|
}
|
|
@@ -18777,27 +19013,6 @@ function createReplyToThread(deps) {
|
|
|
18777
19013
|
context: diagnosticsContext
|
|
18778
19014
|
});
|
|
18779
19015
|
}
|
|
18780
|
-
markConversationMessage(
|
|
18781
|
-
preparedState.conversation,
|
|
18782
|
-
preparedState.userMessageId,
|
|
18783
|
-
{
|
|
18784
|
-
replied: true,
|
|
18785
|
-
skippedReason: void 0
|
|
18786
|
-
}
|
|
18787
|
-
);
|
|
18788
|
-
upsertConversationMessage(preparedState.conversation, {
|
|
18789
|
-
id: generateConversationId("assistant"),
|
|
18790
|
-
role: "assistant",
|
|
18791
|
-
text: normalizeConversationText(reply.text) || "[empty response]",
|
|
18792
|
-
createdAtMs: Date.now(),
|
|
18793
|
-
author: {
|
|
18794
|
-
userName: botConfig.userName,
|
|
18795
|
-
isBot: true
|
|
18796
|
-
},
|
|
18797
|
-
meta: {
|
|
18798
|
-
replied: true
|
|
18799
|
-
}
|
|
18800
|
-
});
|
|
18801
19016
|
const artifactStatePatch = reply.artifactStatePatch ? { ...reply.artifactStatePatch } : {};
|
|
18802
19017
|
const reactionPerformed = reply.diagnostics.toolCalls.includes(
|
|
18803
19018
|
"slackMessageAddReaction"
|
|
@@ -18870,20 +19085,18 @@ function createReplyToThread(deps) {
|
|
|
18870
19085
|
if (titleUpdateResult) {
|
|
18871
19086
|
artifactStatePatch.assistantTitleSourceMessageId = titleUpdateResult;
|
|
18872
19087
|
}
|
|
18873
|
-
const
|
|
18874
|
-
|
|
18875
|
-
|
|
19088
|
+
const completedState = buildDeliveredTurnStatePatch({
|
|
19089
|
+
artifactStatePatch,
|
|
19090
|
+
artifacts: preparedState.artifacts,
|
|
18876
19091
|
conversation: preparedState.conversation,
|
|
18877
|
-
|
|
19092
|
+
reply,
|
|
18878
19093
|
sessionId: turnId,
|
|
18879
|
-
|
|
19094
|
+
userMessageId: preparedState.userMessageId
|
|
18880
19095
|
});
|
|
18881
19096
|
await persistThreadState(thread, {
|
|
18882
|
-
|
|
18883
|
-
conversation: preparedState.conversation,
|
|
18884
|
-
sandboxId: reply.sandboxId,
|
|
18885
|
-
sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash
|
|
19097
|
+
...completedState
|
|
18886
19098
|
});
|
|
19099
|
+
preparedState.conversation = completedState.conversation;
|
|
18887
19100
|
persistedAtLeastOnce = true;
|
|
18888
19101
|
if (shouldEmitDevAgentTrace()) {
|
|
18889
19102
|
logInfo(
|
|
@@ -19021,12 +19234,30 @@ function createReplyToThread(deps) {
|
|
|
19021
19234
|
markTurnFailed({
|
|
19022
19235
|
conversation: preparedState.conversation,
|
|
19023
19236
|
nowMs: Date.now(),
|
|
19237
|
+
sessionId: turnId,
|
|
19024
19238
|
userMessageId: preparedState.userMessageId,
|
|
19025
19239
|
markConversationMessage: (conversation, messageId, patch) => {
|
|
19026
19240
|
markConversationMessage(conversation, messageId, patch);
|
|
19027
19241
|
},
|
|
19028
19242
|
updateConversationStats
|
|
19029
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
|
+
}
|
|
19030
19261
|
await persistThreadState(thread, {
|
|
19031
19262
|
conversation: preparedState.conversation
|
|
19032
19263
|
});
|
|
@@ -20159,22 +20390,67 @@ async function resolveBuildPluginConfig() {
|
|
|
20159
20390
|
try {
|
|
20160
20391
|
const mod = await import("#junior/config");
|
|
20161
20392
|
return mod.plugins;
|
|
20162
|
-
} catch {
|
|
20163
|
-
|
|
20164
|
-
|
|
20165
|
-
|
|
20166
|
-
|
|
20167
|
-
|
|
20168
|
-
}
|
|
20393
|
+
} catch (error) {
|
|
20394
|
+
if (!isMissingVirtualConfig(error)) {
|
|
20395
|
+
throw error;
|
|
20396
|
+
}
|
|
20397
|
+
const packages = readEnvPluginPackages();
|
|
20398
|
+
if (packages) {
|
|
20399
|
+
return { packages };
|
|
20169
20400
|
}
|
|
20170
20401
|
return void 0;
|
|
20171
20402
|
}
|
|
20172
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
|
+
}
|
|
20173
20439
|
async function createApp(options) {
|
|
20174
20440
|
const pluginConfig = options?.plugins ?? await resolveBuildPluginConfig();
|
|
20175
|
-
|
|
20176
|
-
setPluginConfig(pluginConfig);
|
|
20177
|
-
|
|
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
|
+
}
|
|
20178
20454
|
const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
|
|
20179
20455
|
const app = new Hono();
|
|
20180
20456
|
app.onError((err, c) => {
|