@sentry/junior 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1311 -1284
- package/dist/chat/agent-dispatch/store.d.ts +4 -2
- package/dist/chat/agent-dispatch/types.d.ts +0 -1
- package/dist/chat/conversation-privacy.d.ts +23 -0
- package/dist/chat/logging.d.ts +2 -0
- package/dist/chat/mcp/tool-manager.d.ts +18 -5
- package/dist/chat/mcp/tool-name.d.ts +2 -0
- package/dist/chat/pi/client.d.ts +2 -0
- package/dist/chat/pi/derived-state.d.ts +5 -0
- package/dist/chat/pi/traced-stream.d.ts +5 -1
- package/dist/chat/prompt.d.ts +3 -9
- package/dist/chat/respond-helpers.d.ts +5 -3
- package/dist/chat/respond.d.ts +1 -0
- package/dist/chat/runtime/conversation-message.d.ts +10 -0
- package/dist/chat/runtime/processing-reaction.d.ts +2 -4
- package/dist/chat/runtime/reply-executor.d.ts +13 -16
- package/dist/chat/runtime/slack-runtime.d.ts +19 -32
- package/dist/chat/runtime/thread-state.d.ts +1 -1
- package/dist/chat/runtime/turn-input.d.ts +29 -0
- package/dist/chat/runtime/turn-preparation.d.ts +4 -24
- package/dist/chat/runtime/turn.d.ts +2 -3
- package/dist/chat/sentry-links.d.ts +4 -0
- package/dist/chat/services/context-compaction.d.ts +3 -4
- package/dist/chat/services/pending-auth.d.ts +1 -1
- package/dist/chat/services/subscribed-reply-policy.d.ts +2 -13
- package/dist/chat/services/timeout-resume.d.ts +1 -2
- package/dist/chat/services/turn-session-record.d.ts +82 -0
- package/dist/chat/slack/assistant-thread/title.d.ts +4 -1
- package/dist/chat/state/artifacts.d.ts +1 -0
- package/dist/chat/state/conversation.d.ts +0 -1
- package/dist/chat/state/session-log.d.ts +117 -0
- package/dist/chat/state/ttl.d.ts +2 -0
- package/dist/chat/state/turn-session.d.ts +89 -0
- package/dist/chat/tools/advisor/tool.d.ts +2 -0
- package/dist/chat/tools/agent-tools.d.ts +2 -1
- package/dist/chat/tools/skill/call-mcp-tool.d.ts +7 -3
- package/dist/chat/tools/skill/search-mcp-tools.d.ts +15 -3
- package/dist/chat/tools/types.d.ts +0 -1
- package/dist/{chunk-AA5TIFN5.js → chunk-FKEKRBUB.js} +267 -735
- package/dist/{chunk-TTUY467K.js → chunk-H652GMDH.js} +30 -14
- package/dist/chunk-I4FDGMFI.js +950 -0
- package/dist/{chunk-D3G3YOU4.js → chunk-ITOW4DED.js} +1 -1
- package/dist/chunk-QDGD5WVN.js +708 -0
- package/dist/cli/check.js +2 -2
- package/dist/cli/init.js +0 -1
- package/dist/cli/snapshot-warmup.js +5 -3
- package/dist/instrumentation.js +3 -0
- package/dist/reporting.d.ts +113 -0
- package/dist/reporting.js +390 -0
- package/package.json +25 -11
- package/dist/chat/services/turn-checkpoint.d.ts +0 -74
- package/dist/chat/state/pi-session-message-store.d.ts +0 -15
- package/dist/chat/state/turn-session-store.d.ts +0 -49
- package/dist/handlers/diagnostics-dashboard.d.ts +0 -2
- package/dist/handlers/diagnostics.d.ts +0 -2
package/dist/app.js
CHANGED
|
@@ -1,38 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GET,
|
|
3
|
+
abandonAgentTurnSessionRecord,
|
|
4
|
+
buildSentryConversationUrl,
|
|
5
|
+
commitMessages,
|
|
6
|
+
failAgentTurnSessionRecord,
|
|
7
|
+
getAgentTurnSessionRecord,
|
|
8
|
+
loadConnectedMcpProviders,
|
|
9
|
+
loadProjection,
|
|
10
|
+
recordAgentTurnSessionSummary,
|
|
11
|
+
recordAuthorizationCompleted,
|
|
12
|
+
recordAuthorizationRequested,
|
|
13
|
+
recordMcpProviderConnected,
|
|
14
|
+
upsertAgentTurnSessionRecord
|
|
15
|
+
} from "./chunk-I4FDGMFI.js";
|
|
1
16
|
import {
|
|
2
17
|
discoverSkills,
|
|
3
18
|
findSkillByName,
|
|
4
19
|
loadSkillsByName,
|
|
5
20
|
parseSkillInvocation
|
|
6
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-ITOW4DED.js";
|
|
7
22
|
import {
|
|
8
|
-
ACTIVE_LOCK_TTL_MS,
|
|
9
|
-
GEN_AI_PROVIDER_NAME,
|
|
10
|
-
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
11
23
|
SANDBOX_DATA_ROOT,
|
|
12
24
|
SANDBOX_SKILLS_ROOT,
|
|
13
25
|
SANDBOX_WORKSPACE_ROOT,
|
|
14
|
-
botConfig,
|
|
15
26
|
buildNonInteractiveShellScript,
|
|
27
|
+
createSandboxInstance,
|
|
28
|
+
getRuntimeDependencyProfileHash,
|
|
29
|
+
getVercelSandboxCredentials,
|
|
30
|
+
isSnapshotMissingError,
|
|
31
|
+
resolveRuntimeDependencySnapshot,
|
|
32
|
+
runNonInteractiveCommand,
|
|
33
|
+
sandboxSkillDir,
|
|
34
|
+
sandboxSkillFile
|
|
35
|
+
} from "./chunk-QDGD5WVN.js";
|
|
36
|
+
import {
|
|
37
|
+
ACTIVE_LOCK_TTL_MS,
|
|
38
|
+
GEN_AI_PROVIDER_NAME,
|
|
39
|
+
GEN_AI_SERVER_ADDRESS,
|
|
40
|
+
GEN_AI_SERVER_PORT,
|
|
41
|
+
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
42
|
+
botConfig,
|
|
16
43
|
completeObject,
|
|
17
44
|
completeText,
|
|
18
|
-
createSandboxInstance,
|
|
19
45
|
getGatewayApiKey,
|
|
20
46
|
getPiGatewayApiKeyOverride,
|
|
21
|
-
getRuntimeDependencyProfileHash,
|
|
22
47
|
getRuntimeMetadata,
|
|
23
48
|
getSlackBotToken,
|
|
24
49
|
getSlackClientId,
|
|
25
50
|
getSlackClientSecret,
|
|
26
51
|
getSlackSigningSecret,
|
|
27
52
|
getStateAdapter,
|
|
28
|
-
|
|
29
|
-
|
|
53
|
+
parseSlackThreadId,
|
|
54
|
+
resolveConversationPrivacy,
|
|
30
55
|
resolveGatewayModel,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
56
|
+
resolveSlackChannelIdFromMessage,
|
|
57
|
+
resolveSlackChannelIdFromThreadId,
|
|
58
|
+
toGenAiMessageMetadata,
|
|
59
|
+
toGenAiMessagesTraceAttributes,
|
|
60
|
+
toGenAiPayloadMetadata,
|
|
61
|
+
toGenAiPayloadTraceAttributes,
|
|
62
|
+
toGenAiTextMetadata
|
|
63
|
+
} from "./chunk-FKEKRBUB.js";
|
|
36
64
|
import {
|
|
37
65
|
CredentialUnavailableError,
|
|
38
66
|
buildOAuthTokenRequest,
|
|
@@ -49,7 +77,6 @@ import {
|
|
|
49
77
|
getPluginDefinition,
|
|
50
78
|
getPluginMcpProviders,
|
|
51
79
|
getPluginOAuthConfig,
|
|
52
|
-
getPluginPackageContent,
|
|
53
80
|
getPluginProviders,
|
|
54
81
|
hasRequiredOAuthScope,
|
|
55
82
|
isPluginConfigKey,
|
|
@@ -59,6 +86,7 @@ import {
|
|
|
59
86
|
logException,
|
|
60
87
|
logInfo,
|
|
61
88
|
logWarn,
|
|
89
|
+
normalizeGenAiFinishReason,
|
|
62
90
|
parseOAuthTokenResponse,
|
|
63
91
|
resolveAuthTokenPlaceholder,
|
|
64
92
|
resolvePluginCommandEnv,
|
|
@@ -71,7 +99,7 @@ import {
|
|
|
71
99
|
toOptionalString,
|
|
72
100
|
withContext,
|
|
73
101
|
withSpan
|
|
74
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-H652GMDH.js";
|
|
75
103
|
import {
|
|
76
104
|
sentry_exports
|
|
77
105
|
} from "./chunk-Z3YD6NHK.js";
|
|
@@ -468,252 +496,13 @@ function createAgentPluginHookRunner(input = {}) {
|
|
|
468
496
|
};
|
|
469
497
|
}
|
|
470
498
|
|
|
471
|
-
// src/handlers/diagnostics.ts
|
|
472
|
-
import { readFileSync } from "fs";
|
|
473
|
-
import path from "path";
|
|
474
|
-
function readDescriptionText() {
|
|
475
|
-
try {
|
|
476
|
-
const raw = readFileSync(
|
|
477
|
-
path.join(homeDir(), "DESCRIPTION.md"),
|
|
478
|
-
"utf8"
|
|
479
|
-
).trim();
|
|
480
|
-
return raw || void 0;
|
|
481
|
-
} catch {
|
|
482
|
-
return void 0;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
async function GET() {
|
|
486
|
-
const packagedContent = getPluginPackageContent();
|
|
487
|
-
const skills = await discoverSkills();
|
|
488
|
-
return Response.json({
|
|
489
|
-
cwd: process.cwd(),
|
|
490
|
-
homeDir: homeDir(),
|
|
491
|
-
descriptionText: readDescriptionText(),
|
|
492
|
-
providers: getPluginProviders().map((plugin) => plugin.manifest.name),
|
|
493
|
-
skills: skills.map((skill) => ({
|
|
494
|
-
name: skill.name,
|
|
495
|
-
pluginProvider: skill.pluginProvider
|
|
496
|
-
})),
|
|
497
|
-
packagedContent
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// src/chat/xml.ts
|
|
502
|
-
function escapeXml(value) {
|
|
503
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// src/handlers/health.ts
|
|
507
|
-
function GET2() {
|
|
508
|
-
return Response.json({
|
|
509
|
-
status: "ok",
|
|
510
|
-
service: "junior",
|
|
511
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// src/handlers/diagnostics-dashboard.ts
|
|
516
|
-
async function GET3() {
|
|
517
|
-
let health;
|
|
518
|
-
let discovery;
|
|
519
|
-
try {
|
|
520
|
-
const res = await GET2();
|
|
521
|
-
health = {
|
|
522
|
-
ok: res.ok,
|
|
523
|
-
data: await res.json()
|
|
524
|
-
};
|
|
525
|
-
} catch (e) {
|
|
526
|
-
health = { ok: false, error: String(e) };
|
|
527
|
-
}
|
|
528
|
-
try {
|
|
529
|
-
const res = await GET();
|
|
530
|
-
if (res.ok) {
|
|
531
|
-
discovery = {
|
|
532
|
-
ok: true,
|
|
533
|
-
data: await res.json()
|
|
534
|
-
};
|
|
535
|
-
} else {
|
|
536
|
-
discovery = { ok: false, error: `${res.status} ${res.statusText}` };
|
|
537
|
-
}
|
|
538
|
-
} catch (e) {
|
|
539
|
-
discovery = { ok: false, error: String(e) };
|
|
540
|
-
}
|
|
541
|
-
const d = discovery.ok ? discovery.data : null;
|
|
542
|
-
let html = `<!DOCTYPE html>
|
|
543
|
-
<html lang="en">
|
|
544
|
-
<head>
|
|
545
|
-
<meta charset="utf-8" />
|
|
546
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
547
|
-
<title>Junior</title>
|
|
548
|
-
<style>
|
|
549
|
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
550
|
-
body {
|
|
551
|
-
font-family: "SF Mono", "Cascadia Code", "Fira Code", Menlo, monospace;
|
|
552
|
-
background: #0d1117; color: #c9d1d9; padding: 2rem;
|
|
553
|
-
font-size: 14px; line-height: 1.6;
|
|
554
|
-
}
|
|
555
|
-
h1 { color: #58a6ff; font-size: 1.1rem; margin-bottom: 0.25rem; }
|
|
556
|
-
.subtitle { color: #8b949e; font-size: 0.85rem; margin-bottom: 1.5rem; }
|
|
557
|
-
.section { max-width: 720px; margin-bottom: 1.25rem; }
|
|
558
|
-
.section-title {
|
|
559
|
-
color: #8b949e; font-size: 0.75rem; text-transform: uppercase;
|
|
560
|
-
letter-spacing: 0.08em; margin-bottom: 0.5rem; padding-bottom: 0.25rem;
|
|
561
|
-
border-bottom: 1px solid #21262d;
|
|
562
|
-
}
|
|
563
|
-
.status-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.35rem; }
|
|
564
|
-
.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
565
|
-
.dot-ok { background: #2dd4bf; }
|
|
566
|
-
.dot-err { background: #f87171; }
|
|
567
|
-
.label { color: #8b949e; }
|
|
568
|
-
.value { color: #e6edf3; }
|
|
569
|
-
.detail-row { display: flex; gap: 0.5rem; margin-bottom: 0.25rem; font-size: 0.85rem; }
|
|
570
|
-
.detail-key { color: #8b949e; min-width: 7rem; }
|
|
571
|
-
.detail-val { color: #c9d1d9; }
|
|
572
|
-
.skill-grid { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
|
573
|
-
.skill-tag {
|
|
574
|
-
background: #161b22; border: 1px solid #30363d; border-radius: 4px;
|
|
575
|
-
padding: 0.2rem 0.5rem; font-size: 0.8rem; color: #c9d1d9;
|
|
576
|
-
}
|
|
577
|
-
.skill-provider { color: #8b949e; font-size: 0.7rem; margin-left: 0.15rem; }
|
|
578
|
-
.provider-list, .package-list { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
|
579
|
-
.provider-tag {
|
|
580
|
-
background: #1b2332; border: 1px solid #1f3a5f; border-radius: 4px;
|
|
581
|
-
padding: 0.2rem 0.5rem; font-size: 0.8rem; color: #58a6ff;
|
|
582
|
-
}
|
|
583
|
-
.package-tag {
|
|
584
|
-
background: #1a1e2a; border: 1px solid #2d3548; border-radius: 4px;
|
|
585
|
-
padding: 0.2rem 0.5rem; font-size: 0.78rem; color: #a5b4cf;
|
|
586
|
-
}
|
|
587
|
-
.endpoint-list { list-style: none; }
|
|
588
|
-
.endpoint-list li { margin-bottom: 0.2rem; font-size: 0.85rem; }
|
|
589
|
-
.method {
|
|
590
|
-
display: inline-block; font-size: 0.7rem; font-weight: 600;
|
|
591
|
-
padding: 0.1rem 0.35rem; border-radius: 3px; margin-right: 0.4rem;
|
|
592
|
-
min-width: 2.5rem; text-align: center;
|
|
593
|
-
}
|
|
594
|
-
.method-get { background: #1b3a2d; color: #2dd4bf; }
|
|
595
|
-
.method-post { background: #3b2e1a; color: #f0b952; }
|
|
596
|
-
.endpoint-link { color: #c9d1d9; text-decoration: none; }
|
|
597
|
-
.endpoint-link:hover { color: #58a6ff; text-decoration: underline; }
|
|
598
|
-
.error-msg { color: #f87171; font-size: 0.85rem; }
|
|
599
|
-
</style>
|
|
600
|
-
</head>
|
|
601
|
-
<body>
|
|
602
|
-
<h1>> junior</h1>`;
|
|
603
|
-
if (d?.descriptionText) {
|
|
604
|
-
html += `
|
|
605
|
-
<div class="subtitle">${escapeXml(String(d.descriptionText))}</div>`;
|
|
606
|
-
}
|
|
607
|
-
html += `
|
|
608
|
-
<div class="section">
|
|
609
|
-
<div class="section-title">Status</div>
|
|
610
|
-
<div class="status-row">
|
|
611
|
-
<span class="dot ${health.ok ? "dot-ok" : "dot-err"}"></span>
|
|
612
|
-
<span class="value">${health.ok ? "Healthy" : "Unreachable"}</span>`;
|
|
613
|
-
if (health.ok && health.data?.timestamp) {
|
|
614
|
-
html += `
|
|
615
|
-
<span class="label">· ${escapeXml(new Date(health.data.timestamp).toLocaleTimeString())}</span>`;
|
|
616
|
-
}
|
|
617
|
-
html += `
|
|
618
|
-
</div>`;
|
|
619
|
-
if (d) {
|
|
620
|
-
html += `
|
|
621
|
-
<div class="detail-row"><span class="detail-key">service</span><span class="detail-val">${escapeXml(String(health.data?.service ?? "junior"))}</span></div>`;
|
|
622
|
-
html += `
|
|
623
|
-
<div class="detail-row"><span class="detail-key">cwd</span><span class="detail-val">${escapeXml(String(d.cwd))}</span></div>`;
|
|
624
|
-
html += `
|
|
625
|
-
<div class="detail-row"><span class="detail-key">home</span><span class="detail-val">${escapeXml(String(d.homeDir))}</span></div>`;
|
|
626
|
-
}
|
|
627
|
-
html += `
|
|
628
|
-
</div>`;
|
|
629
|
-
const endpoints = [
|
|
630
|
-
{ method: "GET", path: "/health" },
|
|
631
|
-
{ method: "GET", path: "/api/info" },
|
|
632
|
-
{ method: "GET", path: "/api/oauth/callback/mcp/:provider" },
|
|
633
|
-
{ method: "GET", path: "/api/oauth/callback/:provider" },
|
|
634
|
-
{ method: "POST", path: "/api/internal/agent-dispatch" },
|
|
635
|
-
{ method: "GET", path: "/api/internal/heartbeat" },
|
|
636
|
-
{ method: "POST", path: "/api/webhooks/:platform" }
|
|
637
|
-
];
|
|
638
|
-
html += `
|
|
639
|
-
<div class="section">
|
|
640
|
-
<div class="section-title">Endpoints</div>
|
|
641
|
-
<ul class="endpoint-list">`;
|
|
642
|
-
for (const ep of endpoints) {
|
|
643
|
-
const cls = ep.method === "GET" ? "method-get" : "method-post";
|
|
644
|
-
const link = ep.path.includes(":") ? `<span>${escapeXml(ep.path)}</span>` : `<a class="endpoint-link" href="${escapeXml(ep.path)}" target="_blank">${escapeXml(ep.path)}</a>`;
|
|
645
|
-
html += `
|
|
646
|
-
<li><span class="method ${cls}">${escapeXml(ep.method)}</span>${link}</li>`;
|
|
647
|
-
}
|
|
648
|
-
html += `
|
|
649
|
-
</ul>
|
|
650
|
-
</div>`;
|
|
651
|
-
if (d) {
|
|
652
|
-
const providers = d.providers;
|
|
653
|
-
const packagedContent = d.packagedContent;
|
|
654
|
-
const skills = d.skills;
|
|
655
|
-
if (providers?.length) {
|
|
656
|
-
html += `
|
|
657
|
-
<div class="section">
|
|
658
|
-
<div class="section-title">Plugins <span class="label">(${providers.length})</span></div>
|
|
659
|
-
<div class="provider-list">`;
|
|
660
|
-
for (const p of providers) {
|
|
661
|
-
html += `
|
|
662
|
-
<span class="provider-tag">${escapeXml(p)}</span>`;
|
|
663
|
-
}
|
|
664
|
-
html += `
|
|
665
|
-
</div>`;
|
|
666
|
-
if (packagedContent?.packageNames?.length) {
|
|
667
|
-
html += `
|
|
668
|
-
<div style="margin-top:0.5rem"><div class="package-list">`;
|
|
669
|
-
for (const pkg of packagedContent.packageNames) {
|
|
670
|
-
html += `
|
|
671
|
-
<span class="package-tag">${escapeXml(pkg)}</span>`;
|
|
672
|
-
}
|
|
673
|
-
html += `
|
|
674
|
-
</div></div>`;
|
|
675
|
-
}
|
|
676
|
-
html += `
|
|
677
|
-
</div>`;
|
|
678
|
-
}
|
|
679
|
-
if (skills?.length) {
|
|
680
|
-
html += `
|
|
681
|
-
<div class="section">
|
|
682
|
-
<div class="section-title">Skills <span class="label">(${skills.length})</span></div>
|
|
683
|
-
<div class="skill-grid">`;
|
|
684
|
-
for (const s of skills) {
|
|
685
|
-
html += `
|
|
686
|
-
<span class="skill-tag">${escapeXml(s.name)}`;
|
|
687
|
-
if (s.pluginProvider) {
|
|
688
|
-
html += ` <span class="skill-provider">${escapeXml(s.pluginProvider)}</span>`;
|
|
689
|
-
}
|
|
690
|
-
html += `</span>`;
|
|
691
|
-
}
|
|
692
|
-
html += `
|
|
693
|
-
</div>
|
|
694
|
-
</div>`;
|
|
695
|
-
}
|
|
696
|
-
} else if (!discovery.ok) {
|
|
697
|
-
html += `
|
|
698
|
-
<div class="section">
|
|
699
|
-
<div class="section-title">Discovery</div>
|
|
700
|
-
<span class="error-msg">unavailable · ${escapeXml(discovery.error ?? "unknown")}</span>
|
|
701
|
-
</div>`;
|
|
702
|
-
}
|
|
703
|
-
html += `
|
|
704
|
-
</body>
|
|
705
|
-
</html>`;
|
|
706
|
-
return new Response(html, {
|
|
707
|
-
headers: { "content-type": "text/html; charset=utf-8" }
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
|
|
711
499
|
// src/chat/respond.ts
|
|
712
500
|
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
501
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
713
502
|
|
|
714
503
|
// src/chat/prompt.ts
|
|
715
504
|
import fs from "fs";
|
|
716
|
-
import
|
|
505
|
+
import path from "path";
|
|
717
506
|
|
|
718
507
|
// src/chat/turn-context-tag.ts
|
|
719
508
|
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
@@ -999,6 +788,11 @@ var slackOutputPolicy = {
|
|
|
999
788
|
maxInlineLines: MAX_INLINE_LINES
|
|
1000
789
|
};
|
|
1001
790
|
|
|
791
|
+
// src/chat/xml.ts
|
|
792
|
+
function escapeXml(value) {
|
|
793
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
794
|
+
}
|
|
795
|
+
|
|
1002
796
|
// src/chat/prompt.ts
|
|
1003
797
|
var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
|
|
1004
798
|
function getLoggedMarkdownFiles() {
|
|
@@ -1127,7 +921,7 @@ function formatAvailableSkillsForPrompt(skills, invocation) {
|
|
|
1127
921
|
if (autoSelectable.length > 0) {
|
|
1128
922
|
const available = [
|
|
1129
923
|
"<available-skills>",
|
|
1130
|
-
"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."
|
|
924
|
+
"Scan before answering. Load the most specific matching skill; do not answer from memory when a skill fits. A request that names a skill, plugin, provider, or account matching a skill name is a skill match. If none fits, do not load a skill."
|
|
1131
925
|
];
|
|
1132
926
|
for (const skill of autoSelectable) {
|
|
1133
927
|
available.push(...formatSkillEntry(skill));
|
|
@@ -1148,26 +942,6 @@ function formatAvailableSkillsForPrompt(skills, invocation) {
|
|
|
1148
942
|
}
|
|
1149
943
|
return sections.length > 0 ? sections.join("\n") : null;
|
|
1150
944
|
}
|
|
1151
|
-
function formatLoadedSkillsForPrompt(skills) {
|
|
1152
|
-
if (skills.length === 0) {
|
|
1153
|
-
return null;
|
|
1154
|
-
}
|
|
1155
|
-
const lines = ["<loaded-skills>"];
|
|
1156
|
-
for (const skill of skills) {
|
|
1157
|
-
const skillDir = workspaceSkillDir(skill.name);
|
|
1158
|
-
lines.push(
|
|
1159
|
-
` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
|
|
1160
|
-
);
|
|
1161
|
-
lines.push(
|
|
1162
|
-
`Skill directory: ${escapeXml(skillDir)}. Resolve relative paths there; for skill-owned bash commands, cd there first or use absolute paths.`
|
|
1163
|
-
);
|
|
1164
|
-
lines.push("");
|
|
1165
|
-
lines.push(skill.body);
|
|
1166
|
-
lines.push(" </skill>");
|
|
1167
|
-
}
|
|
1168
|
-
lines.push("</loaded-skills>");
|
|
1169
|
-
return lines.join("\n");
|
|
1170
|
-
}
|
|
1171
945
|
function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
1172
946
|
if (catalogs.length === 0) {
|
|
1173
947
|
return null;
|
|
@@ -1213,7 +987,7 @@ function formatReferenceFilesLines() {
|
|
|
1213
987
|
return null;
|
|
1214
988
|
}
|
|
1215
989
|
return files.map((filePath) => {
|
|
1216
|
-
const name =
|
|
990
|
+
const name = path.basename(filePath);
|
|
1217
991
|
return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
|
|
1218
992
|
});
|
|
1219
993
|
}
|
|
@@ -1255,7 +1029,7 @@ function formatConfigurationLines(configuration) {
|
|
|
1255
1029
|
);
|
|
1256
1030
|
}
|
|
1257
1031
|
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.";
|
|
1258
|
-
var TURN_CONTEXT_HEADER = "
|
|
1032
|
+
var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
|
|
1259
1033
|
var TOOL_POLICY_RULES = [
|
|
1260
1034
|
"- 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.",
|
|
1261
1035
|
"- 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.",
|
|
@@ -1275,7 +1049,7 @@ var TOOL_CALL_STYLE_RULES = [
|
|
|
1275
1049
|
];
|
|
1276
1050
|
var SKILL_POLICY_RULES = [
|
|
1277
1051
|
"- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
|
|
1278
|
-
"- Load one skill at a time. After `loadSkill`, follow the instructions
|
|
1052
|
+
"- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
|
|
1279
1053
|
];
|
|
1280
1054
|
var EXECUTION_CONTRACT_RULES = [
|
|
1281
1055
|
"- Actionable request: act in this turn.",
|
|
@@ -1386,12 +1160,6 @@ function buildContextSection(params) {
|
|
|
1386
1160
|
])
|
|
1387
1161
|
);
|
|
1388
1162
|
}
|
|
1389
|
-
if (params.turnState === "resumed") {
|
|
1390
|
-
blocks.push([
|
|
1391
|
-
"<turn-state>resumed</turn-state>",
|
|
1392
|
-
"This turn continues from a prior checkpoint. Prior tool results and assistant messages are already in the conversation history."
|
|
1393
|
-
]);
|
|
1394
|
-
}
|
|
1395
1163
|
if (params.invocation) {
|
|
1396
1164
|
blocks.push(
|
|
1397
1165
|
renderTag("explicit-skill-trigger", [
|
|
@@ -1415,10 +1183,6 @@ function buildCapabilitiesSection(params) {
|
|
|
1415
1183
|
if (availableSkills) {
|
|
1416
1184
|
blocks.push(availableSkills);
|
|
1417
1185
|
}
|
|
1418
|
-
const loadedSkills = formatLoadedSkillsForPrompt(params.activeSkills);
|
|
1419
|
-
if (loadedSkills) {
|
|
1420
|
-
blocks.push(loadedSkills);
|
|
1421
|
-
}
|
|
1422
1186
|
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
1423
1187
|
params.activeMcpCatalogs
|
|
1424
1188
|
);
|
|
@@ -1445,13 +1209,13 @@ function buildSystemPrompt() {
|
|
|
1445
1209
|
return STATIC_SYSTEM_PROMPT;
|
|
1446
1210
|
}
|
|
1447
1211
|
function buildTurnContextPrompt(params) {
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1212
|
+
const includeSessionContext = params.includeSessionContext ?? true;
|
|
1213
|
+
if (!includeSessionContext) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
const runtimeSections = [
|
|
1452
1217
|
buildCapabilitiesSection({
|
|
1453
1218
|
availableSkills: params.availableSkills,
|
|
1454
|
-
activeSkills: params.activeSkills,
|
|
1455
1219
|
activeMcpCatalogs: params.activeMcpCatalogs ?? [],
|
|
1456
1220
|
invocation: params.invocation,
|
|
1457
1221
|
toolGuidance: params.toolGuidance ?? []
|
|
@@ -1460,10 +1224,18 @@ function buildTurnContextPrompt(params) {
|
|
|
1460
1224
|
requester: params.requester,
|
|
1461
1225
|
artifactState: params.artifactState,
|
|
1462
1226
|
configuration: params.configuration,
|
|
1463
|
-
invocation: params.invocation
|
|
1464
|
-
turnState: params.turnState
|
|
1227
|
+
invocation: params.invocation
|
|
1465
1228
|
}),
|
|
1466
|
-
buildRuntimeSection(params.runtime ?? {})
|
|
1229
|
+
buildRuntimeSection(params.runtime ?? {})
|
|
1230
|
+
].filter((section) => Boolean(section));
|
|
1231
|
+
if (runtimeSections.length === 0) {
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
const sections = [
|
|
1235
|
+
`<${TURN_CONTEXT_TAG}>`,
|
|
1236
|
+
TURN_CONTEXT_HEADER,
|
|
1237
|
+
"The current user instruction appears after this block in the same message.",
|
|
1238
|
+
...runtimeSections,
|
|
1467
1239
|
`</${TURN_CONTEXT_TAG}>`
|
|
1468
1240
|
].filter((section) => Boolean(section));
|
|
1469
1241
|
return sections.join("\n\n");
|
|
@@ -1864,12 +1636,12 @@ async function maybeExecuteJrRpcCustomCommand(command, deps) {
|
|
|
1864
1636
|
|
|
1865
1637
|
// src/chat/sandbox/skill-sandbox.ts
|
|
1866
1638
|
import fs2 from "fs/promises";
|
|
1867
|
-
import
|
|
1639
|
+
import path2 from "path";
|
|
1868
1640
|
var MAX_SKILL_FILE_BYTES = 256 * 1024;
|
|
1869
1641
|
var DEFAULT_MAX_SKILL_FILE_CHARS = 2e4;
|
|
1870
1642
|
var DEFAULT_MAX_SKILL_LIST_ENTRIES = 200;
|
|
1871
1643
|
function normalizePathForOutput(value) {
|
|
1872
|
-
return value.split(
|
|
1644
|
+
return value.split(path2.sep).join("/");
|
|
1873
1645
|
}
|
|
1874
1646
|
function normalizeSkillName(value) {
|
|
1875
1647
|
return value.trim().toLowerCase();
|
|
@@ -1878,12 +1650,12 @@ function resolvePathWithinRoot(root, relativePath) {
|
|
|
1878
1650
|
if (!relativePath.trim()) {
|
|
1879
1651
|
throw new Error("Path must not be empty.");
|
|
1880
1652
|
}
|
|
1881
|
-
if (
|
|
1653
|
+
if (path2.isAbsolute(relativePath)) {
|
|
1882
1654
|
throw new Error("Absolute paths are not allowed.");
|
|
1883
1655
|
}
|
|
1884
|
-
const resolvedRoot =
|
|
1885
|
-
const resolvedPath =
|
|
1886
|
-
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${
|
|
1656
|
+
const resolvedRoot = path2.resolve(root);
|
|
1657
|
+
const resolvedPath = path2.resolve(resolvedRoot, relativePath);
|
|
1658
|
+
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path2.sep}`)) {
|
|
1887
1659
|
throw new Error("Path escapes the skill directory.");
|
|
1888
1660
|
}
|
|
1889
1661
|
return resolvedPath;
|
|
@@ -1963,7 +1735,7 @@ var SkillSandbox = class {
|
|
|
1963
1735
|
1,
|
|
1964
1736
|
Math.min(params.maxEntries ?? DEFAULT_MAX_SKILL_LIST_ENTRIES, 1e3)
|
|
1965
1737
|
);
|
|
1966
|
-
const root =
|
|
1738
|
+
const root = path2.resolve(skill.skillPath);
|
|
1967
1739
|
const targetDirectory = resolvePathWithinRoot(root, directory);
|
|
1968
1740
|
const targetStats = await fs2.stat(targetDirectory);
|
|
1969
1741
|
if (!targetStats.isDirectory()) {
|
|
@@ -1979,9 +1751,9 @@ var SkillSandbox = class {
|
|
|
1979
1751
|
});
|
|
1980
1752
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
1981
1753
|
for (const child of children) {
|
|
1982
|
-
const absolutePath =
|
|
1754
|
+
const absolutePath = path2.join(currentDirectory, child.name);
|
|
1983
1755
|
const relativePath = normalizePathForOutput(
|
|
1984
|
-
|
|
1756
|
+
path2.relative(root, absolutePath)
|
|
1985
1757
|
);
|
|
1986
1758
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
1987
1759
|
continue;
|
|
@@ -2004,7 +1776,7 @@ var SkillSandbox = class {
|
|
|
2004
1776
|
}
|
|
2005
1777
|
}
|
|
2006
1778
|
const relativeDirectory = normalizePathForOutput(
|
|
2007
|
-
|
|
1779
|
+
path2.relative(root, targetDirectory) || "."
|
|
2008
1780
|
);
|
|
2009
1781
|
return {
|
|
2010
1782
|
skillName: skill.name,
|
|
@@ -2019,7 +1791,7 @@ var SkillSandbox = class {
|
|
|
2019
1791
|
1,
|
|
2020
1792
|
Math.min(params.maxChars ?? DEFAULT_MAX_SKILL_FILE_CHARS, 1e5)
|
|
2021
1793
|
);
|
|
2022
|
-
const root =
|
|
1794
|
+
const root = path2.resolve(skill.skillPath);
|
|
2023
1795
|
const targetPath = resolvePathWithinRoot(root, params.filePath);
|
|
2024
1796
|
const stats = await fs2.stat(targetPath);
|
|
2025
1797
|
if (!stats.isFile()) {
|
|
@@ -2034,7 +1806,7 @@ var SkillSandbox = class {
|
|
|
2034
1806
|
const truncated = raw.length > maxChars;
|
|
2035
1807
|
return {
|
|
2036
1808
|
skillName: skill.name,
|
|
2037
|
-
path: normalizePathForOutput(
|
|
1809
|
+
path: normalizePathForOutput(path2.relative(root, targetPath)),
|
|
2038
1810
|
content: truncated ? raw.slice(0, maxChars) : raw,
|
|
2039
1811
|
truncated
|
|
2040
1812
|
};
|
|
@@ -2402,6 +2174,14 @@ var McpToolManager = class {
|
|
|
2402
2174
|
(left, right) => left.localeCompare(right)
|
|
2403
2175
|
);
|
|
2404
2176
|
}
|
|
2177
|
+
/** List configured MCP providers for discovery without connecting to them. */
|
|
2178
|
+
getAvailableProviderCatalog() {
|
|
2179
|
+
return [...this.pluginsByProvider.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([provider, plugin]) => ({
|
|
2180
|
+
provider,
|
|
2181
|
+
description: plugin.manifest.description,
|
|
2182
|
+
active: this.activeProviders.has(provider)
|
|
2183
|
+
}));
|
|
2184
|
+
}
|
|
2405
2185
|
async activateForSkill(skill) {
|
|
2406
2186
|
if (!skill.pluginProvider) {
|
|
2407
2187
|
return false;
|
|
@@ -2452,8 +2232,9 @@ var McpToolManager = class {
|
|
|
2452
2232
|
throw firstError;
|
|
2453
2233
|
}
|
|
2454
2234
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2235
|
+
/** Return descriptors for all active MCP provider tools, optionally filtered by provider. */
|
|
2236
|
+
getActiveToolCatalog(options = {}) {
|
|
2237
|
+
return this.getResolvedActiveTools(options).map(
|
|
2457
2238
|
(tool2) => this.toToolDescriptor(tool2)
|
|
2458
2239
|
);
|
|
2459
2240
|
}
|
|
@@ -2573,30 +2354,17 @@ var McpToolManager = class {
|
|
|
2573
2354
|
this.activeProviders.delete(provider);
|
|
2574
2355
|
return true;
|
|
2575
2356
|
}
|
|
2576
|
-
/** Return all active ManagedMcpTool objects
|
|
2577
|
-
getResolvedActiveTools(
|
|
2357
|
+
/** Return all active ManagedMcpTool objects, optionally filtered by provider. */
|
|
2358
|
+
getResolvedActiveTools(options = {}) {
|
|
2578
2359
|
const resolved = [];
|
|
2579
2360
|
for (const provider of this.getActiveProviders()) {
|
|
2580
2361
|
if (options.provider && provider !== options.provider) {
|
|
2581
2362
|
continue;
|
|
2582
2363
|
}
|
|
2583
|
-
resolved.push(...this.
|
|
2364
|
+
resolved.push(...this.toolsByProvider.get(provider) ?? []);
|
|
2584
2365
|
}
|
|
2585
2366
|
return resolved;
|
|
2586
2367
|
}
|
|
2587
|
-
resolveProviderTools(provider, skills) {
|
|
2588
|
-
const providerTools = this.toolsByProvider.get(provider) ?? [];
|
|
2589
|
-
if (providerTools.length === 0) {
|
|
2590
|
-
return [];
|
|
2591
|
-
}
|
|
2592
|
-
const relevantSkills = skills.filter(
|
|
2593
|
-
(skill) => skill.pluginProvider === provider
|
|
2594
|
-
);
|
|
2595
|
-
if (relevantSkills.length === 0) {
|
|
2596
|
-
return [];
|
|
2597
|
-
}
|
|
2598
|
-
return providerTools;
|
|
2599
|
-
}
|
|
2600
2368
|
toToolDescriptor(tool2) {
|
|
2601
2369
|
return {
|
|
2602
2370
|
name: tool2.name,
|
|
@@ -2614,6 +2382,136 @@ function toOptionalRecord(value) {
|
|
|
2614
2382
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2615
2383
|
}
|
|
2616
2384
|
|
|
2385
|
+
// src/chat/mcp/tool-name.ts
|
|
2386
|
+
function parseMcpProviderFromToolName(toolName) {
|
|
2387
|
+
if (!toolName.startsWith("mcp__")) return void 0;
|
|
2388
|
+
const afterPrefix = toolName.slice("mcp__".length);
|
|
2389
|
+
const delimiterIndex = afterPrefix.indexOf("__");
|
|
2390
|
+
return delimiterIndex > 0 ? afterPrefix.slice(0, delimiterIndex) : void 0;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
// src/chat/pi/derived-state.ts
|
|
2394
|
+
var MCP_BRIDGE_TOOLS = /* @__PURE__ */ new Set(["callMcpTool", "searchMcpTools"]);
|
|
2395
|
+
function isRecord3(value) {
|
|
2396
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2397
|
+
}
|
|
2398
|
+
function providerFromToolName(value) {
|
|
2399
|
+
if (typeof value !== "string") {
|
|
2400
|
+
return void 0;
|
|
2401
|
+
}
|
|
2402
|
+
return parseMcpProviderFromToolName(value);
|
|
2403
|
+
}
|
|
2404
|
+
function addString(values, value) {
|
|
2405
|
+
if (typeof value === "string" && value.trim()) {
|
|
2406
|
+
values.add(value.trim());
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
function getToolName(value) {
|
|
2410
|
+
return typeof value.toolName === "string" ? value.toolName : typeof value.name === "string" ? value.name : void 0;
|
|
2411
|
+
}
|
|
2412
|
+
function addBridgeToolProvider(toolName, value, providers) {
|
|
2413
|
+
const bridgeTool = toolName && MCP_BRIDGE_TOOLS.has(toolName) ? toolName : void 0;
|
|
2414
|
+
if (bridgeTool === "searchMcpTools") {
|
|
2415
|
+
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
2416
|
+
const args = value[argsKey];
|
|
2417
|
+
if (isRecord3(args)) {
|
|
2418
|
+
addString(providers, args.provider);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
addString(providers, value.provider);
|
|
2422
|
+
}
|
|
2423
|
+
if (bridgeTool === "callMcpTool") {
|
|
2424
|
+
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
2425
|
+
const args = value[argsKey];
|
|
2426
|
+
if (isRecord3(args)) {
|
|
2427
|
+
addString(providers, providerFromToolName(args.tool_name));
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
addString(providers, providerFromToolName(value.tool_name));
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
function addMcpResultProvider(message, providers) {
|
|
2434
|
+
const toolName = typeof message.toolName === "string" ? message.toolName : void 0;
|
|
2435
|
+
if (message.isError === true) {
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
if (toolName === "loadSkill") {
|
|
2439
|
+
if (isRecord3(message.details)) {
|
|
2440
|
+
addString(providers, message.details.mcp_provider);
|
|
2441
|
+
}
|
|
2442
|
+
addString(providers, message.mcp_provider);
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
if (toolName === "searchMcpTools") {
|
|
2446
|
+
if (isRecord3(message.details)) {
|
|
2447
|
+
addString(providers, message.details.provider);
|
|
2448
|
+
if (Array.isArray(message.details.tools)) {
|
|
2449
|
+
for (const tool2 of message.details.tools) {
|
|
2450
|
+
if (isRecord3(tool2)) {
|
|
2451
|
+
addString(providers, providerFromToolName(tool2.tool_name));
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
addString(providers, message.provider);
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
if (toolName === "callMcpTool") {
|
|
2460
|
+
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
2461
|
+
const args = message[argsKey];
|
|
2462
|
+
if (isRecord3(args)) {
|
|
2463
|
+
addString(providers, providerFromToolName(args.tool_name));
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
if (isRecord3(message.details)) {
|
|
2467
|
+
addString(providers, message.details.provider);
|
|
2468
|
+
addString(providers, providerFromToolName(message.details.tool_name));
|
|
2469
|
+
}
|
|
2470
|
+
addString(providers, providerFromToolName(message.tool_name));
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
function scanMcpProviders(message, providers) {
|
|
2474
|
+
if (!isRecord3(message)) {
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
if (message.role === "toolResult") {
|
|
2478
|
+
addMcpResultProvider(message, providers);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const content = message.content;
|
|
2482
|
+
if (!Array.isArray(content)) {
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
for (const part of content) {
|
|
2486
|
+
if (!isRecord3(part)) {
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
addBridgeToolProvider(getToolName(part), part, providers);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
function scanLoadedSkills(message, skills) {
|
|
2493
|
+
if (isRecord3(message) && message.role === "toolResult" && message.toolName === "loadSkill" && message.isError !== true) {
|
|
2494
|
+
if (isRecord3(message.details)) {
|
|
2495
|
+
addString(skills, message.details.skill_name);
|
|
2496
|
+
}
|
|
2497
|
+
addString(skills, message.skill_name);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
function inferActiveMcpProvidersFromPiMessages(messages) {
|
|
2501
|
+
const providers = /* @__PURE__ */ new Set();
|
|
2502
|
+
for (const message of messages ?? []) {
|
|
2503
|
+
scanMcpProviders(message, providers);
|
|
2504
|
+
}
|
|
2505
|
+
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
2506
|
+
}
|
|
2507
|
+
function inferLoadedSkillNamesFromPiMessages(messages) {
|
|
2508
|
+
const skills = /* @__PURE__ */ new Set();
|
|
2509
|
+
for (const message of messages ?? []) {
|
|
2510
|
+
scanLoadedSkills(message, skills);
|
|
2511
|
+
}
|
|
2512
|
+
return [...skills].sort((left, right) => left.localeCompare(right));
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2617
2515
|
// src/chat/tools/definition.ts
|
|
2618
2516
|
function tool(definition) {
|
|
2619
2517
|
return definition;
|
|
@@ -2646,7 +2544,7 @@ function createBashTool() {
|
|
|
2646
2544
|
}
|
|
2647
2545
|
|
|
2648
2546
|
// src/chat/tools/sandbox/file-utils.ts
|
|
2649
|
-
import
|
|
2547
|
+
import path3 from "path";
|
|
2650
2548
|
|
|
2651
2549
|
// src/chat/tools/execution/tool-input-error.ts
|
|
2652
2550
|
var ToolInputError = class extends Error {
|
|
@@ -2742,12 +2640,12 @@ function matchesGlob(relativePath, pattern) {
|
|
|
2742
2640
|
if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
|
|
2743
2641
|
return true;
|
|
2744
2642
|
}
|
|
2745
|
-
return !pattern.includes("/") && matcher.test(
|
|
2643
|
+
return !pattern.includes("/") && matcher.test(path3.posix.basename(relativePath));
|
|
2746
2644
|
}
|
|
2747
2645
|
function resolveWorkspacePath(input, fallback = ".") {
|
|
2748
2646
|
const requested = (input ?? "").trim() || fallback;
|
|
2749
|
-
const absolute = requested.startsWith("/") ? requested :
|
|
2750
|
-
const normalized =
|
|
2647
|
+
const absolute = requested.startsWith("/") ? requested : path3.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
|
|
2648
|
+
const normalized = path3.posix.normalize(absolute);
|
|
2751
2649
|
if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
2752
2650
|
throw new ToolInputError(
|
|
2753
2651
|
`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
|
|
@@ -2776,7 +2674,7 @@ async function collectFiles(params) {
|
|
|
2776
2674
|
throw error;
|
|
2777
2675
|
}
|
|
2778
2676
|
for (const entry of entries) {
|
|
2779
|
-
const fullPath =
|
|
2677
|
+
const fullPath = path3.posix.join(dirPath, entry);
|
|
2780
2678
|
let stat2;
|
|
2781
2679
|
try {
|
|
2782
2680
|
stat2 = await params.fs.stat(fullPath);
|
|
@@ -2794,7 +2692,7 @@ async function collectFiles(params) {
|
|
|
2794
2692
|
if (limitReached) return;
|
|
2795
2693
|
continue;
|
|
2796
2694
|
}
|
|
2797
|
-
const relativePath =
|
|
2695
|
+
const relativePath = path3.posix.relative(params.root, fullPath);
|
|
2798
2696
|
if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
|
|
2799
2697
|
files.push(fullPath);
|
|
2800
2698
|
if (params.limit && files.length >= params.limit) {
|
|
@@ -2819,7 +2717,7 @@ async function collectFiles(params) {
|
|
|
2819
2717
|
throw error;
|
|
2820
2718
|
}
|
|
2821
2719
|
if (!stat.isDirectory()) {
|
|
2822
|
-
const relativePath =
|
|
2720
|
+
const relativePath = path3.posix.basename(params.root);
|
|
2823
2721
|
return {
|
|
2824
2722
|
files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
|
|
2825
2723
|
limitReached: false,
|
|
@@ -3102,7 +3000,7 @@ function createEditFileTool() {
|
|
|
3102
3000
|
}
|
|
3103
3001
|
|
|
3104
3002
|
// src/chat/tools/sandbox/find-files.ts
|
|
3105
|
-
import
|
|
3003
|
+
import path4 from "path";
|
|
3106
3004
|
import { Type as Type3 } from "@sinclair/typebox";
|
|
3107
3005
|
var DEFAULT_FIND_LIMIT = 1e3;
|
|
3108
3006
|
async function findFiles(params) {
|
|
@@ -3124,7 +3022,7 @@ async function findFiles(params) {
|
|
|
3124
3022
|
});
|
|
3125
3023
|
}
|
|
3126
3024
|
const relativePaths = files.map(
|
|
3127
|
-
(filePath) =>
|
|
3025
|
+
(filePath) => path4.posix.relative(root, filePath)
|
|
3128
3026
|
);
|
|
3129
3027
|
const bounded = truncateText(
|
|
3130
3028
|
relativePaths.length > 0 ? relativePaths.join("\n") : "No files found matching pattern"
|
|
@@ -3189,7 +3087,7 @@ function createFindFilesTool() {
|
|
|
3189
3087
|
}
|
|
3190
3088
|
|
|
3191
3089
|
// src/chat/tools/sandbox/grep.ts
|
|
3192
|
-
import
|
|
3090
|
+
import path5 from "path";
|
|
3193
3091
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
3194
3092
|
var DEFAULT_GREP_LIMIT = 100;
|
|
3195
3093
|
var MAX_GREP_LINE_CHARS = 500;
|
|
@@ -3261,7 +3159,7 @@ async function grepFiles(params) {
|
|
|
3261
3159
|
continue;
|
|
3262
3160
|
}
|
|
3263
3161
|
const lines = normalizeToLf(content).split("\n");
|
|
3264
|
-
const relativePath = files.length === 1 && filePath === root ?
|
|
3162
|
+
const relativePath = files.length === 1 && filePath === root ? path5.posix.basename(filePath) : path5.posix.relative(root, filePath);
|
|
3265
3163
|
const matchedLines = [];
|
|
3266
3164
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
3267
3165
|
if (!lineMatches({
|
|
@@ -3388,7 +3286,7 @@ function createGrepTool() {
|
|
|
3388
3286
|
}
|
|
3389
3287
|
|
|
3390
3288
|
// src/chat/tools/sandbox/attach-file.ts
|
|
3391
|
-
import
|
|
3289
|
+
import path6 from "path";
|
|
3392
3290
|
import { Type as Type5 } from "@sinclair/typebox";
|
|
3393
3291
|
var MAX_ATTACH_FILE_BYTES = 10 * 1024 * 1024;
|
|
3394
3292
|
var MIME_BY_EXTENSION = {
|
|
@@ -3410,20 +3308,20 @@ function normalizeSandboxPath(inputPath) {
|
|
|
3410
3308
|
if (!trimmed) {
|
|
3411
3309
|
throw new Error("path is required");
|
|
3412
3310
|
}
|
|
3413
|
-
if (
|
|
3311
|
+
if (path6.posix.isAbsolute(trimmed)) {
|
|
3414
3312
|
return trimmed;
|
|
3415
3313
|
}
|
|
3416
|
-
return
|
|
3314
|
+
return path6.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
|
|
3417
3315
|
}
|
|
3418
3316
|
function sanitizeFilename(value, fallbackPath) {
|
|
3419
3317
|
const candidate = (value ?? "").trim();
|
|
3420
3318
|
if (candidate) {
|
|
3421
|
-
const base =
|
|
3319
|
+
const base = path6.posix.basename(candidate);
|
|
3422
3320
|
if (base && base !== "." && base !== "..") {
|
|
3423
3321
|
return base;
|
|
3424
3322
|
}
|
|
3425
3323
|
}
|
|
3426
|
-
const derived =
|
|
3324
|
+
const derived = path6.posix.basename(fallbackPath);
|
|
3427
3325
|
if (derived && derived !== "." && derived !== "..") {
|
|
3428
3326
|
return derived;
|
|
3429
3327
|
}
|
|
@@ -3434,7 +3332,7 @@ function inferMimeType(filename, explicitMimeType) {
|
|
|
3434
3332
|
if (explicit) {
|
|
3435
3333
|
return explicit;
|
|
3436
3334
|
}
|
|
3437
|
-
const ext =
|
|
3335
|
+
const ext = path6.extname(filename).toLowerCase();
|
|
3438
3336
|
return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
3439
3337
|
}
|
|
3440
3338
|
async function detectMimeType(sandbox, targetPath) {
|
|
@@ -3481,7 +3379,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
3481
3379
|
const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
|
|
3482
3380
|
if (!fileBuffer) {
|
|
3483
3381
|
const generatedFile = hooks.getGeneratedFile?.(
|
|
3484
|
-
|
|
3382
|
+
path6.posix.basename(targetPath)
|
|
3485
3383
|
);
|
|
3486
3384
|
if (generatedFile) {
|
|
3487
3385
|
hooks.onGeneratedFiles?.([generatedFile]);
|
|
@@ -3529,7 +3427,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
3529
3427
|
}
|
|
3530
3428
|
|
|
3531
3429
|
// src/chat/tools/sandbox/list-dir.ts
|
|
3532
|
-
import
|
|
3430
|
+
import path7 from "path";
|
|
3533
3431
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
3534
3432
|
var DEFAULT_LIST_LIMIT = 500;
|
|
3535
3433
|
async function listDir(params) {
|
|
@@ -3565,7 +3463,7 @@ async function listDir(params) {
|
|
|
3565
3463
|
entryLimitReached = true;
|
|
3566
3464
|
break;
|
|
3567
3465
|
}
|
|
3568
|
-
const entryPath =
|
|
3466
|
+
const entryPath = path7.posix.join(dirPath, entry);
|
|
3569
3467
|
try {
|
|
3570
3468
|
const entryStat = await params.fs.stat(entryPath);
|
|
3571
3469
|
output.push(`${entry}${entryStat.isDirectory() ? "/" : ""}`);
|
|
@@ -3805,9 +3703,9 @@ function resolveMcpArguments(input) {
|
|
|
3805
3703
|
}
|
|
3806
3704
|
return {};
|
|
3807
3705
|
}
|
|
3808
|
-
function createCallMcpToolTool(mcpToolManager
|
|
3706
|
+
function createCallMcpToolTool(mcpToolManager) {
|
|
3809
3707
|
return tool({
|
|
3810
|
-
description: "Call an active MCP tool by exact tool_name. Use
|
|
3708
|
+
description: "Call an active MCP tool by exact tool_name. Use searchMcpTools to discover tool names and schemas; copy required provider fields into arguments. Do not call with only tool_name unless the discovered tool has no arguments. Authorization is handled by the runtime when required.",
|
|
3811
3709
|
inputSchema: Type8.Object(
|
|
3812
3710
|
{
|
|
3813
3711
|
tool_name: Type8.String({
|
|
@@ -3824,7 +3722,11 @@ function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
|
|
|
3824
3722
|
),
|
|
3825
3723
|
execute: async (input) => {
|
|
3826
3724
|
const { tool_name } = input;
|
|
3827
|
-
const
|
|
3725
|
+
const provider = parseMcpProviderFromToolName(tool_name);
|
|
3726
|
+
if (provider) {
|
|
3727
|
+
await mcpToolManager.activateProvider(provider);
|
|
3728
|
+
}
|
|
3729
|
+
const mcpTool = mcpToolManager.getResolvedActiveTools().find((candidate) => candidate.name === tool_name);
|
|
3828
3730
|
if (!mcpTool) {
|
|
3829
3731
|
throw new Error(`MCP tool is not active for this turn: ${tool_name}`);
|
|
3830
3732
|
}
|
|
@@ -4073,6 +3975,33 @@ function scoreTool(toolDef, query) {
|
|
|
4073
3975
|
}
|
|
4074
3976
|
return score;
|
|
4075
3977
|
}
|
|
3978
|
+
function scoreProvider(provider, query) {
|
|
3979
|
+
const normalizedQuery = normalize(query);
|
|
3980
|
+
if (!normalizedQuery) {
|
|
3981
|
+
return 0;
|
|
3982
|
+
}
|
|
3983
|
+
const normalizedName = normalize(provider.provider);
|
|
3984
|
+
const text = normalize([provider.provider, provider.description].join(" "));
|
|
3985
|
+
let score = 0;
|
|
3986
|
+
if (normalizedName === normalizedQuery) {
|
|
3987
|
+
score += 100;
|
|
3988
|
+
}
|
|
3989
|
+
if (normalizedName.includes(normalizedQuery)) {
|
|
3990
|
+
score += 50;
|
|
3991
|
+
}
|
|
3992
|
+
if (text.includes(normalizedQuery)) {
|
|
3993
|
+
score += 25;
|
|
3994
|
+
}
|
|
3995
|
+
for (const term of normalizedQuery.split(/\s+/).filter(Boolean)) {
|
|
3996
|
+
if (normalizedName.includes(term)) {
|
|
3997
|
+
score += 12;
|
|
3998
|
+
}
|
|
3999
|
+
if (text.includes(term)) {
|
|
4000
|
+
score += 4;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
return score;
|
|
4004
|
+
}
|
|
4076
4005
|
function searchMcpCatalog(tools, query) {
|
|
4077
4006
|
if (!normalize(query)) {
|
|
4078
4007
|
return [...tools].sort(
|
|
@@ -4091,9 +4020,28 @@ function searchMcpCatalog(tools, query) {
|
|
|
4091
4020
|
return left.tool.name.localeCompare(right.tool.name);
|
|
4092
4021
|
}).map((ranked) => ranked.tool);
|
|
4093
4022
|
}
|
|
4094
|
-
function
|
|
4023
|
+
function searchProviderCatalog(providers, query) {
|
|
4024
|
+
const sorted = [...providers].sort(
|
|
4025
|
+
(left, right) => left.provider.localeCompare(right.provider)
|
|
4026
|
+
);
|
|
4027
|
+
if (!normalize(query)) {
|
|
4028
|
+
return sorted;
|
|
4029
|
+
}
|
|
4030
|
+
return sorted.map(
|
|
4031
|
+
(provider) => ({
|
|
4032
|
+
provider,
|
|
4033
|
+
score: scoreProvider(provider, query)
|
|
4034
|
+
})
|
|
4035
|
+
).filter((ranked) => ranked.score > 0).sort((left, right) => {
|
|
4036
|
+
if (right.score !== left.score) {
|
|
4037
|
+
return right.score - left.score;
|
|
4038
|
+
}
|
|
4039
|
+
return left.provider.provider.localeCompare(right.provider.provider);
|
|
4040
|
+
}).map((ranked) => ranked.provider);
|
|
4041
|
+
}
|
|
4042
|
+
function createSearchMcpToolsTool(mcpToolManager) {
|
|
4095
4043
|
return tool({
|
|
4096
|
-
description: "List or search active MCP tools and
|
|
4044
|
+
description: "List or search MCP providers and active MCP tools. When provider is supplied and not yet active, Junior connects to it on demand and returns tool descriptors including schemas. Without provider, returns active tools plus matching configured providers without connecting. Use when choosing a provider tool or when callMcpTool arguments are unclear.",
|
|
4097
4045
|
inputSchema: Type10.Object(
|
|
4098
4046
|
{
|
|
4099
4047
|
query: Type10.Optional(
|
|
@@ -4105,7 +4053,7 @@ function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
|
|
|
4105
4053
|
provider: Type10.Optional(
|
|
4106
4054
|
Type10.String({
|
|
4107
4055
|
minLength: 1,
|
|
4108
|
-
description: "Optional provider name to list or search within."
|
|
4056
|
+
description: "Optional provider name to list or search within. If configured but not yet connected, Junior activates it on demand."
|
|
4109
4057
|
})
|
|
4110
4058
|
),
|
|
4111
4059
|
max_results: Type10.Optional(
|
|
@@ -4119,8 +4067,10 @@ function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
|
|
|
4119
4067
|
{ additionalProperties: false }
|
|
4120
4068
|
),
|
|
4121
4069
|
execute: async ({ query, provider, max_results }) => {
|
|
4070
|
+
if (provider) {
|
|
4071
|
+
await mcpToolManager.activateProvider(provider);
|
|
4072
|
+
}
|
|
4122
4073
|
const catalog = mcpToolManager.getActiveToolCatalog(
|
|
4123
|
-
getActiveSkills(),
|
|
4124
4074
|
provider ? { provider } : {}
|
|
4125
4075
|
);
|
|
4126
4076
|
const maxResults = max_results ?? DEFAULT_MAX_RESULTS;
|
|
@@ -4128,11 +4078,16 @@ function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
|
|
|
4128
4078
|
0,
|
|
4129
4079
|
maxResults
|
|
4130
4080
|
);
|
|
4081
|
+
const providers = provider ? [] : searchProviderCatalog(
|
|
4082
|
+
mcpToolManager.getAvailableProviderCatalog(),
|
|
4083
|
+
query ?? ""
|
|
4084
|
+
).slice(0, maxResults);
|
|
4131
4085
|
return {
|
|
4132
4086
|
query: query ?? null,
|
|
4133
4087
|
provider: provider ?? null,
|
|
4134
4088
|
total_active_tools: catalog.length,
|
|
4135
4089
|
returned_tools: matches.length,
|
|
4090
|
+
available_providers: providers,
|
|
4136
4091
|
tools: matches.map(toExposedToolSummary)
|
|
4137
4092
|
};
|
|
4138
4093
|
}
|
|
@@ -4167,11 +4122,11 @@ function sliceFileContent(params) {
|
|
|
4167
4122
|
} : {}
|
|
4168
4123
|
};
|
|
4169
4124
|
}
|
|
4170
|
-
function missingFileResult(
|
|
4125
|
+
function missingFileResult(path10) {
|
|
4171
4126
|
return {
|
|
4172
4127
|
content: "",
|
|
4173
4128
|
error: "not_found",
|
|
4174
|
-
path:
|
|
4129
|
+
path: path10,
|
|
4175
4130
|
success: false
|
|
4176
4131
|
};
|
|
4177
4132
|
}
|
|
@@ -6745,6 +6700,18 @@ function refreshRuntimeTurnContext(messages, turnContextPrompt) {
|
|
|
6745
6700
|
nextMessages[index] = updated;
|
|
6746
6701
|
return nextMessages;
|
|
6747
6702
|
}
|
|
6703
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
6704
|
+
const content = getUserMessageContent(messages[index]);
|
|
6705
|
+
if (!content) {
|
|
6706
|
+
continue;
|
|
6707
|
+
}
|
|
6708
|
+
const nextMessages = [...messages];
|
|
6709
|
+
nextMessages[index] = {
|
|
6710
|
+
...messages[index],
|
|
6711
|
+
content: [{ type: "text", text: turnContextPrompt }, ...content]
|
|
6712
|
+
};
|
|
6713
|
+
return nextMessages;
|
|
6714
|
+
}
|
|
6748
6715
|
return [
|
|
6749
6716
|
...messages,
|
|
6750
6717
|
{
|
|
@@ -6754,6 +6721,13 @@ function refreshRuntimeTurnContext(messages, turnContextPrompt) {
|
|
|
6754
6721
|
}
|
|
6755
6722
|
];
|
|
6756
6723
|
}
|
|
6724
|
+
function hasRuntimeTurnContext(messages) {
|
|
6725
|
+
return messages.some(
|
|
6726
|
+
(message) => getUserMessageContent(message)?.some(
|
|
6727
|
+
(part) => isRuntimeTurnContextPart(part, RUNTIME_TURN_CONTEXT_START)
|
|
6728
|
+
)
|
|
6729
|
+
);
|
|
6730
|
+
}
|
|
6757
6731
|
function stripRuntimeTurnContext(messages) {
|
|
6758
6732
|
return messages.flatMap((message) => {
|
|
6759
6733
|
const content = getUserMessageContent(message);
|
|
@@ -6808,9 +6782,11 @@ function trimTrailingAssistantMessages(messages) {
|
|
|
6808
6782
|
return end === messages.length ? [...messages] : messages.slice(0, end);
|
|
6809
6783
|
}
|
|
6810
6784
|
|
|
6785
|
+
// src/chat/state/ttl.ts
|
|
6786
|
+
var JUNIOR_THREAD_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6787
|
+
|
|
6811
6788
|
// src/chat/tools/advisor/session-store.ts
|
|
6812
|
-
|
|
6813
|
-
var ADVISOR_SESSION_TTL_MS = THREAD_STATE_TTL_MS;
|
|
6789
|
+
var ADVISOR_SESSION_TTL_MS = JUNIOR_THREAD_STATE_TTL_MS;
|
|
6814
6790
|
function cloneMessages(messages) {
|
|
6815
6791
|
return structuredClone(messages);
|
|
6816
6792
|
}
|
|
@@ -6926,20 +6902,33 @@ function createAdvisorTool(context) {
|
|
|
6926
6902
|
);
|
|
6927
6903
|
}
|
|
6928
6904
|
const conversationId = context.conversationId;
|
|
6905
|
+
const conversationPrivacy = context.conversationPrivacy ?? "private";
|
|
6906
|
+
const requestText = [
|
|
6907
|
+
"<advisor-task>",
|
|
6908
|
+
escapeXml(advisorQuestion),
|
|
6909
|
+
"</advisor-task>",
|
|
6910
|
+
"",
|
|
6911
|
+
"<executor-context>",
|
|
6912
|
+
escapeXml(advisorContext),
|
|
6913
|
+
"</executor-context>"
|
|
6914
|
+
].join("\n");
|
|
6915
|
+
const advisorInputMessage = {
|
|
6916
|
+
role: "user",
|
|
6917
|
+
content: [
|
|
6918
|
+
{
|
|
6919
|
+
type: "text",
|
|
6920
|
+
text: requestText
|
|
6921
|
+
}
|
|
6922
|
+
]
|
|
6923
|
+
};
|
|
6924
|
+
const advisorInputMessagesAttribute = serializeGenAiAttribute(
|
|
6925
|
+
conversationPrivacy !== "public" ? [toGenAiMessageMetadata(advisorInputMessage)] : [advisorInputMessage]
|
|
6926
|
+
);
|
|
6929
6927
|
return await withSpan(
|
|
6930
|
-
|
|
6928
|
+
`invoke_agent ${context.config.modelId}`,
|
|
6931
6929
|
"gen_ai.invoke_agent",
|
|
6932
6930
|
spanContext,
|
|
6933
6931
|
async () => {
|
|
6934
|
-
const requestText = [
|
|
6935
|
-
"<advisor-task>",
|
|
6936
|
-
escapeXml(advisorQuestion),
|
|
6937
|
-
"</advisor-task>",
|
|
6938
|
-
"",
|
|
6939
|
-
"<executor-context>",
|
|
6940
|
-
escapeXml(advisorContext),
|
|
6941
|
-
"</executor-context>"
|
|
6942
|
-
].join("\n");
|
|
6943
6932
|
const requestMessage = {
|
|
6944
6933
|
role: "user",
|
|
6945
6934
|
content: [{ type: "text", text: requestText }],
|
|
@@ -6979,7 +6968,15 @@ function createAdvisorTool(context) {
|
|
|
6979
6968
|
}
|
|
6980
6969
|
const assistant = lastAssistantMessage(advisorAgent.state.messages);
|
|
6981
6970
|
const newAdvisorMessages = advisorAgent.state.messages.slice(beforeMessageCount);
|
|
6982
|
-
|
|
6971
|
+
const outputMessages = newAdvisorMessages.filter(isAssistantMessage);
|
|
6972
|
+
const outputMessagesAttribute = serializeGenAiAttribute(
|
|
6973
|
+
conversationPrivacy !== "public" ? outputMessages.map(toGenAiMessageMetadata) : outputMessages
|
|
6974
|
+
);
|
|
6975
|
+
setSpanAttributes({
|
|
6976
|
+
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
6977
|
+
...toGenAiMessagesTraceAttributes("app.ai.output", outputMessages),
|
|
6978
|
+
...extractGenAiUsageAttributes(...newAdvisorMessages)
|
|
6979
|
+
});
|
|
6983
6980
|
if (!assistant || assistant.stopReason === "error" || assistant.stopReason === "aborted") {
|
|
6984
6981
|
setSpanStatus("error");
|
|
6985
6982
|
return failure(
|
|
@@ -7001,9 +6998,17 @@ function createAdvisorTool(context) {
|
|
|
7001
6998
|
return success(memo);
|
|
7002
6999
|
},
|
|
7003
7000
|
{
|
|
7004
|
-
"gen_ai.provider.name":
|
|
7001
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
7005
7002
|
"gen_ai.operation.name": "invoke_agent",
|
|
7006
|
-
"gen_ai.request.model": context.config.modelId
|
|
7003
|
+
"gen_ai.request.model": context.config.modelId,
|
|
7004
|
+
"gen_ai.output.type": "text",
|
|
7005
|
+
"server.address": GEN_AI_SERVER_ADDRESS,
|
|
7006
|
+
"server.port": GEN_AI_SERVER_PORT,
|
|
7007
|
+
"app.conversation.privacy": conversationPrivacy,
|
|
7008
|
+
...toGenAiMessagesTraceAttributes("app.ai.input", [
|
|
7009
|
+
advisorInputMessage
|
|
7010
|
+
]),
|
|
7011
|
+
...advisorInputMessagesAttribute ? { "gen_ai.input.messages": advisorInputMessagesAttribute } : {}
|
|
7007
7012
|
}
|
|
7008
7013
|
);
|
|
7009
7014
|
}
|
|
@@ -7743,15 +7748,9 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
7743
7748
|
if (context.advisor) {
|
|
7744
7749
|
tools.advisor = createAdvisorTool(context.advisor);
|
|
7745
7750
|
}
|
|
7746
|
-
if (context.mcpToolManager
|
|
7747
|
-
tools.searchMcpTools = createSearchMcpToolsTool(
|
|
7748
|
-
|
|
7749
|
-
context.getActiveSkills
|
|
7750
|
-
);
|
|
7751
|
-
tools.callMcpTool = createCallMcpToolTool(
|
|
7752
|
-
context.mcpToolManager,
|
|
7753
|
-
context.getActiveSkills
|
|
7754
|
-
);
|
|
7751
|
+
if (context.mcpToolManager) {
|
|
7752
|
+
tools.searchMcpTools = createSearchMcpToolsTool(context.mcpToolManager);
|
|
7753
|
+
tools.callMcpTool = createCallMcpToolTool(context.mcpToolManager);
|
|
7755
7754
|
}
|
|
7756
7755
|
const { channelCapabilities } = context;
|
|
7757
7756
|
if (channelCapabilities.canCreateCanvas) {
|
|
@@ -7796,49 +7795,73 @@ function resolveChannelCapabilities(channelId) {
|
|
|
7796
7795
|
import {
|
|
7797
7796
|
streamSimple
|
|
7798
7797
|
} from "@earendil-works/pi-ai";
|
|
7799
|
-
function
|
|
7798
|
+
function attributeModeForPrivacy(conversationPrivacy) {
|
|
7799
|
+
return conversationPrivacy === "public" ? "content" : "metadata";
|
|
7800
|
+
}
|
|
7801
|
+
function buildChatStartAttributes(model, context, mode, conversationPrivacy) {
|
|
7800
7802
|
const attributes = {
|
|
7801
7803
|
"gen_ai.operation.name": "chat",
|
|
7802
7804
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
7803
|
-
"gen_ai.request.model": model.id
|
|
7805
|
+
"gen_ai.request.model": model.id,
|
|
7806
|
+
"gen_ai.request.stream": true,
|
|
7807
|
+
"gen_ai.output.type": "text",
|
|
7808
|
+
"server.address": GEN_AI_SERVER_ADDRESS,
|
|
7809
|
+
"server.port": GEN_AI_SERVER_PORT,
|
|
7810
|
+
...conversationPrivacy ? { "app.conversation.privacy": conversationPrivacy } : {},
|
|
7811
|
+
...toGenAiMessagesTraceAttributes("app.ai.input", context.messages)
|
|
7804
7812
|
};
|
|
7805
|
-
const inputMessages = serializeGenAiAttribute(
|
|
7813
|
+
const inputMessages = serializeGenAiAttribute(
|
|
7814
|
+
mode === "metadata" ? context.messages.map(toGenAiMessageMetadata) : context.messages
|
|
7815
|
+
);
|
|
7806
7816
|
if (inputMessages) {
|
|
7807
7817
|
attributes["gen_ai.input.messages"] = inputMessages;
|
|
7808
7818
|
}
|
|
7809
7819
|
if (context.systemPrompt) {
|
|
7810
7820
|
const systemInstructions = serializeGenAiAttribute([
|
|
7811
|
-
{ type: "text", content: context.systemPrompt }
|
|
7821
|
+
mode === "metadata" ? toGenAiTextMetadata(context.systemPrompt) : { type: "text", content: context.systemPrompt }
|
|
7812
7822
|
]);
|
|
7813
7823
|
if (systemInstructions) {
|
|
7814
7824
|
attributes["gen_ai.system_instructions"] = systemInstructions;
|
|
7815
7825
|
}
|
|
7826
|
+
attributes["app.ai.system_instructions.content_chars"] = context.systemPrompt.length;
|
|
7816
7827
|
}
|
|
7817
7828
|
return attributes;
|
|
7818
7829
|
}
|
|
7819
|
-
function buildChatEndAttributes(message) {
|
|
7820
|
-
const attributes = {
|
|
7821
|
-
|
|
7830
|
+
function buildChatEndAttributes(message, mode) {
|
|
7831
|
+
const attributes = {
|
|
7832
|
+
...toGenAiMessagesTraceAttributes("app.ai.output", [message])
|
|
7833
|
+
};
|
|
7834
|
+
const outputMessages = serializeGenAiAttribute(
|
|
7835
|
+
mode === "metadata" ? [toGenAiMessageMetadata(message)] : [message]
|
|
7836
|
+
);
|
|
7822
7837
|
if (outputMessages) {
|
|
7823
7838
|
attributes["gen_ai.output.messages"] = outputMessages;
|
|
7824
7839
|
}
|
|
7825
7840
|
Object.assign(attributes, extractGenAiUsageAttributes(message));
|
|
7826
7841
|
if (message.stopReason) {
|
|
7827
|
-
attributes["gen_ai.response.finish_reasons"] = [
|
|
7842
|
+
attributes["gen_ai.response.finish_reasons"] = [
|
|
7843
|
+
normalizeGenAiFinishReason(message.stopReason)
|
|
7844
|
+
];
|
|
7828
7845
|
}
|
|
7829
7846
|
if (message.model) {
|
|
7830
7847
|
attributes["gen_ai.response.model"] = message.model;
|
|
7831
7848
|
}
|
|
7832
7849
|
return attributes;
|
|
7833
7850
|
}
|
|
7834
|
-
function createTracedStreamFn(
|
|
7851
|
+
function createTracedStreamFn(baseOrOptions = streamSimple) {
|
|
7852
|
+
const base = typeof baseOrOptions === "function" ? baseOrOptions : baseOrOptions.base ?? streamSimple;
|
|
7853
|
+
const mode = attributeModeForPrivacy(
|
|
7854
|
+
typeof baseOrOptions === "function" ? void 0 : baseOrOptions.conversationPrivacy
|
|
7855
|
+
);
|
|
7856
|
+
const conversationPrivacy = typeof baseOrOptions === "function" ? void 0 : baseOrOptions.conversationPrivacy;
|
|
7857
|
+
const effectivePrivacy = conversationPrivacy ?? "private";
|
|
7835
7858
|
return async (model, context, options) => {
|
|
7836
7859
|
const span = sentry_exports.startInactiveSpan({
|
|
7837
7860
|
name: `chat ${model.id}`,
|
|
7838
7861
|
op: "gen_ai.chat",
|
|
7839
7862
|
attributes: {
|
|
7840
7863
|
...getLogContextAttributes(),
|
|
7841
|
-
...buildChatStartAttributes(model, context)
|
|
7864
|
+
...buildChatStartAttributes(model, context, mode, effectivePrivacy)
|
|
7842
7865
|
}
|
|
7843
7866
|
});
|
|
7844
7867
|
try {
|
|
@@ -7850,7 +7873,7 @@ function createTracedStreamFn(base = streamSimple) {
|
|
|
7850
7873
|
(finalMessage) => {
|
|
7851
7874
|
try {
|
|
7852
7875
|
for (const [key, value] of Object.entries(
|
|
7853
|
-
buildChatEndAttributes(finalMessage)
|
|
7876
|
+
buildChatEndAttributes(finalMessage, mode)
|
|
7854
7877
|
)) {
|
|
7855
7878
|
span.setAttribute(key, value);
|
|
7856
7879
|
}
|
|
@@ -8198,8 +8221,8 @@ function sandboxProxyUrl(requesterToken) {
|
|
|
8198
8221
|
"Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
8199
8222
|
);
|
|
8200
8223
|
}
|
|
8201
|
-
const
|
|
8202
|
-
return new URL(
|
|
8224
|
+
const path10 = requesterToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${requesterToken}` : SANDBOX_EGRESS_PROXY_PATH;
|
|
8225
|
+
return new URL(path10, baseUrl).toString();
|
|
8203
8226
|
}
|
|
8204
8227
|
function buildSandboxEgressNetworkPolicy(input) {
|
|
8205
8228
|
const allow = {
|
|
@@ -8447,9 +8470,9 @@ import { createBashTool as createBashTool2 } from "bash-tool";
|
|
|
8447
8470
|
|
|
8448
8471
|
// src/chat/sandbox/skill-sync.ts
|
|
8449
8472
|
import fs3 from "fs/promises";
|
|
8450
|
-
import
|
|
8473
|
+
import path8 from "path";
|
|
8451
8474
|
function toPosixRelative(base, absolute) {
|
|
8452
|
-
return
|
|
8475
|
+
return path8.relative(base, absolute).split(path8.sep).join("/");
|
|
8453
8476
|
}
|
|
8454
8477
|
async function listFilesRecursive(root) {
|
|
8455
8478
|
const queue = [root];
|
|
@@ -8459,7 +8482,7 @@ async function listFilesRecursive(root) {
|
|
|
8459
8482
|
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
8460
8483
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
8461
8484
|
for (const entry of entries) {
|
|
8462
|
-
const absolute =
|
|
8485
|
+
const absolute = path8.join(dir, entry.name);
|
|
8463
8486
|
if (entry.isDirectory()) {
|
|
8464
8487
|
queue.push(absolute);
|
|
8465
8488
|
} else if (entry.isFile()) {
|
|
@@ -8498,7 +8521,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8498
8521
|
});
|
|
8499
8522
|
if (referenceFiles && referenceFiles.length > 0) {
|
|
8500
8523
|
for (const absoluteFile of referenceFiles) {
|
|
8501
|
-
const fileName =
|
|
8524
|
+
const fileName = path8.basename(absoluteFile);
|
|
8502
8525
|
filesToWrite.push({
|
|
8503
8526
|
path: `${SANDBOX_DATA_ROOT}/${fileName}`,
|
|
8504
8527
|
content: await fs3.readFile(absoluteFile)
|
|
@@ -8510,7 +8533,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8510
8533
|
function collectDirectories(filesToWrite, workspaceRoot) {
|
|
8511
8534
|
const directoriesToEnsure = /* @__PURE__ */ new Set();
|
|
8512
8535
|
for (const file of filesToWrite) {
|
|
8513
|
-
const normalizedPath =
|
|
8536
|
+
const normalizedPath = path8.posix.normalize(file.path);
|
|
8514
8537
|
const parts = normalizedPath.split("/").filter(Boolean);
|
|
8515
8538
|
let current = "";
|
|
8516
8539
|
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
@@ -8523,19 +8546,19 @@ function collectDirectories(filesToWrite, workspaceRoot) {
|
|
|
8523
8546
|
).sort((a, b) => a.length - b.length);
|
|
8524
8547
|
}
|
|
8525
8548
|
function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
8526
|
-
const normalizedPath =
|
|
8549
|
+
const normalizedPath = path8.posix.normalize(sandboxPath.trim());
|
|
8527
8550
|
for (const skill of availableSkills) {
|
|
8528
8551
|
const virtualRoot = sandboxSkillDir(skill.name);
|
|
8529
8552
|
if (normalizedPath !== virtualRoot && !normalizedPath.startsWith(`${virtualRoot}/`)) {
|
|
8530
8553
|
continue;
|
|
8531
8554
|
}
|
|
8532
|
-
const relativePath =
|
|
8555
|
+
const relativePath = path8.posix.relative(virtualRoot, normalizedPath);
|
|
8533
8556
|
if (!relativePath || relativePath.startsWith("../")) {
|
|
8534
8557
|
return null;
|
|
8535
8558
|
}
|
|
8536
|
-
const hostRoot =
|
|
8537
|
-
const hostPath =
|
|
8538
|
-
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${
|
|
8559
|
+
const hostRoot = path8.resolve(skill.skillPath);
|
|
8560
|
+
const hostPath = path8.resolve(hostRoot, ...relativePath.split("/"));
|
|
8561
|
+
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path8.sep}`)) {
|
|
8539
8562
|
return null;
|
|
8540
8563
|
}
|
|
8541
8564
|
return hostPath;
|
|
@@ -8543,16 +8566,16 @@ function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
|
8543
8566
|
return null;
|
|
8544
8567
|
}
|
|
8545
8568
|
function resolveHostDataPath(referenceFiles, sandboxPath) {
|
|
8546
|
-
const normalizedPath =
|
|
8569
|
+
const normalizedPath = path8.posix.normalize(sandboxPath.trim());
|
|
8547
8570
|
if (normalizedPath !== SANDBOX_DATA_ROOT && !normalizedPath.startsWith(`${SANDBOX_DATA_ROOT}/`)) {
|
|
8548
8571
|
return null;
|
|
8549
8572
|
}
|
|
8550
|
-
const relativePath =
|
|
8573
|
+
const relativePath = path8.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
|
|
8551
8574
|
if (!relativePath || relativePath.startsWith("../") || relativePath.includes("/")) {
|
|
8552
8575
|
return null;
|
|
8553
8576
|
}
|
|
8554
8577
|
for (const hostFile of referenceFiles) {
|
|
8555
|
-
if (
|
|
8578
|
+
if (path8.basename(hostFile) === relativePath) {
|
|
8556
8579
|
return hostFile;
|
|
8557
8580
|
}
|
|
8558
8581
|
}
|
|
@@ -8790,6 +8813,12 @@ function createSandboxSessionManager(options) {
|
|
|
8790
8813
|
"app.sandbox.recovery.attempted": true,
|
|
8791
8814
|
"app.sandbox.recovery.source": source
|
|
8792
8815
|
});
|
|
8816
|
+
logWarn(
|
|
8817
|
+
"sandbox_unavailable_recreating",
|
|
8818
|
+
traceContext,
|
|
8819
|
+
{ "app.sandbox.recovery.source": source },
|
|
8820
|
+
"Sandbox unavailable; recreating"
|
|
8821
|
+
);
|
|
8793
8822
|
clearSession();
|
|
8794
8823
|
const replacement = await createFreshSandbox();
|
|
8795
8824
|
setSpanAttributes({
|
|
@@ -8929,6 +8958,15 @@ function createSandboxSessionManager(options) {
|
|
|
8929
8958
|
} : {},
|
|
8930
8959
|
...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
|
|
8931
8960
|
});
|
|
8961
|
+
logInfo(
|
|
8962
|
+
"sandbox_hint_discarded_profile_mismatch",
|
|
8963
|
+
traceContext,
|
|
8964
|
+
{
|
|
8965
|
+
...options?.sandboxDependencyProfileHash ? { "app.sandbox.previous_profile_hash": options.sandboxDependencyProfileHash } : {},
|
|
8966
|
+
...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
|
|
8967
|
+
},
|
|
8968
|
+
"Dependency profile changed; discarding sandbox hint and creating fresh session"
|
|
8969
|
+
);
|
|
8932
8970
|
sandboxIdHint = void 0;
|
|
8933
8971
|
};
|
|
8934
8972
|
const tryReuseCachedSandbox = async () => {
|
|
@@ -8970,7 +9008,16 @@ function createSandboxSessionManager(options) {
|
|
|
8970
9008
|
})
|
|
8971
9009
|
)
|
|
8972
9010
|
);
|
|
8973
|
-
} catch {
|
|
9011
|
+
} catch (error) {
|
|
9012
|
+
logWarn(
|
|
9013
|
+
"sandbox_restore_hint_failed",
|
|
9014
|
+
traceContext,
|
|
9015
|
+
{
|
|
9016
|
+
"app.sandbox.hint_id": sandboxIdHint,
|
|
9017
|
+
"app.sandbox.error": error instanceof Error ? error.message : String(error)
|
|
9018
|
+
},
|
|
9019
|
+
"Failed to restore sandbox from hint; will create fresh session"
|
|
9020
|
+
);
|
|
8974
9021
|
return null;
|
|
8975
9022
|
}
|
|
8976
9023
|
try {
|
|
@@ -9786,6 +9833,9 @@ function normalizeToolResult(result, isSandboxResult) {
|
|
|
9786
9833
|
// src/chat/tools/execution/tool-error-handler.ts
|
|
9787
9834
|
import { AgentPluginToolInputError } from "@sentry/junior-plugin-api";
|
|
9788
9835
|
|
|
9836
|
+
// src/chat/services/plugin-auth-orchestration.ts
|
|
9837
|
+
import { THREAD_STATE_TTL_MS } from "chat";
|
|
9838
|
+
|
|
9789
9839
|
// src/chat/mcp/auth-store.ts
|
|
9790
9840
|
var MCP_AUTH_SESSION_PREFIX = "junior:mcp_auth_session";
|
|
9791
9841
|
var MCP_AUTH_CREDENTIALS_PREFIX = "junior:mcp_auth_credentials";
|
|
@@ -10048,317 +10098,6 @@ function buildDeterministicTurnId(messageId) {
|
|
|
10048
10098
|
return `turn_${sanitized}`;
|
|
10049
10099
|
}
|
|
10050
10100
|
|
|
10051
|
-
// src/chat/state/turn-session-store.ts
|
|
10052
|
-
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
|
|
10053
|
-
|
|
10054
|
-
// src/chat/state/pi-session-message-store.ts
|
|
10055
|
-
import { isDeepStrictEqual } from "util";
|
|
10056
|
-
var PI_SESSION_MESSAGE_PREFIX = "junior:pi_session_message";
|
|
10057
|
-
function piSessionMessageKey(scope, index) {
|
|
10058
|
-
return `${PI_SESSION_MESSAGE_PREFIX}:${scope.conversationId}:${scope.sessionId}:${index}`;
|
|
10059
|
-
}
|
|
10060
|
-
function parsePiMessage(value) {
|
|
10061
|
-
return isRecord(value) ? value : void 0;
|
|
10062
|
-
}
|
|
10063
|
-
function normalizeMessageCount(value) {
|
|
10064
|
-
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
10065
|
-
}
|
|
10066
|
-
function countMatchingPrefix(left, right) {
|
|
10067
|
-
const limit = Math.min(left.length, right.length);
|
|
10068
|
-
for (let index = 0; index < limit; index += 1) {
|
|
10069
|
-
if (!isDeepStrictEqual(left[index], right[index])) {
|
|
10070
|
-
return index;
|
|
10071
|
-
}
|
|
10072
|
-
}
|
|
10073
|
-
return limit;
|
|
10074
|
-
}
|
|
10075
|
-
async function loadPiSessionMessages(args) {
|
|
10076
|
-
const stateAdapter = getStateAdapter();
|
|
10077
|
-
await stateAdapter.connect();
|
|
10078
|
-
const messageCount = normalizeMessageCount(args.messageCount);
|
|
10079
|
-
if (messageCount === 0) {
|
|
10080
|
-
return [];
|
|
10081
|
-
}
|
|
10082
|
-
const values = await Promise.all(
|
|
10083
|
-
Array.from(
|
|
10084
|
-
{ length: messageCount },
|
|
10085
|
-
(_, index) => stateAdapter.get(piSessionMessageKey(args, index))
|
|
10086
|
-
)
|
|
10087
|
-
);
|
|
10088
|
-
const messages = [];
|
|
10089
|
-
for (const value of values) {
|
|
10090
|
-
const message = parsePiMessage(value);
|
|
10091
|
-
if (!message) {
|
|
10092
|
-
break;
|
|
10093
|
-
}
|
|
10094
|
-
messages.push(message);
|
|
10095
|
-
}
|
|
10096
|
-
return messages.length === messageCount ? messages : void 0;
|
|
10097
|
-
}
|
|
10098
|
-
async function loadExistingPiSessionMessages(scope, maxCount) {
|
|
10099
|
-
const count = normalizeMessageCount(maxCount);
|
|
10100
|
-
if (count === 0) {
|
|
10101
|
-
return [];
|
|
10102
|
-
}
|
|
10103
|
-
const stateAdapter = getStateAdapter();
|
|
10104
|
-
await stateAdapter.connect();
|
|
10105
|
-
const values = await Promise.all(
|
|
10106
|
-
Array.from(
|
|
10107
|
-
{ length: count },
|
|
10108
|
-
(_, index) => stateAdapter.get(piSessionMessageKey(scope, index))
|
|
10109
|
-
)
|
|
10110
|
-
);
|
|
10111
|
-
const messages = [];
|
|
10112
|
-
for (const value of values) {
|
|
10113
|
-
const message = parsePiMessage(value);
|
|
10114
|
-
if (!message) {
|
|
10115
|
-
break;
|
|
10116
|
-
}
|
|
10117
|
-
messages.push(message);
|
|
10118
|
-
}
|
|
10119
|
-
return messages;
|
|
10120
|
-
}
|
|
10121
|
-
async function commitPiSessionMessages(args) {
|
|
10122
|
-
const stateAdapter = getStateAdapter();
|
|
10123
|
-
await stateAdapter.connect();
|
|
10124
|
-
const existingMessages = await loadExistingPiSessionMessages(
|
|
10125
|
-
{ conversationId: args.conversationId, sessionId: args.sessionId },
|
|
10126
|
-
args.messages.length
|
|
10127
|
-
);
|
|
10128
|
-
const writeFromIndex = countMatchingPrefix(existingMessages, args.messages);
|
|
10129
|
-
await Promise.all(
|
|
10130
|
-
args.messages.slice(writeFromIndex).map(
|
|
10131
|
-
(message, offset) => stateAdapter.set(
|
|
10132
|
-
piSessionMessageKey(args, writeFromIndex + offset),
|
|
10133
|
-
message,
|
|
10134
|
-
args.ttlMs
|
|
10135
|
-
)
|
|
10136
|
-
)
|
|
10137
|
-
);
|
|
10138
|
-
}
|
|
10139
|
-
|
|
10140
|
-
// src/chat/state/turn-session-store.ts
|
|
10141
|
-
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
10142
|
-
var AGENT_TURN_SESSION_TTL_MS = THREAD_STATE_TTL_MS2;
|
|
10143
|
-
function agentTurnSessionKey(conversationId, sessionId) {
|
|
10144
|
-
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
10145
|
-
}
|
|
10146
|
-
function toFiniteNonNegativeNumber(value) {
|
|
10147
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
10148
|
-
}
|
|
10149
|
-
function parseAgentTurnUsage(value) {
|
|
10150
|
-
if (!isRecord(value)) {
|
|
10151
|
-
return void 0;
|
|
10152
|
-
}
|
|
10153
|
-
const usage = {};
|
|
10154
|
-
for (const field of [
|
|
10155
|
-
"inputTokens",
|
|
10156
|
-
"outputTokens",
|
|
10157
|
-
"cachedInputTokens",
|
|
10158
|
-
"cacheCreationTokens",
|
|
10159
|
-
"totalTokens"
|
|
10160
|
-
]) {
|
|
10161
|
-
const count = toFiniteNonNegativeNumber(value[field]);
|
|
10162
|
-
if (count !== void 0) {
|
|
10163
|
-
usage[field] = count;
|
|
10164
|
-
}
|
|
10165
|
-
}
|
|
10166
|
-
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
10167
|
-
}
|
|
10168
|
-
function parseStoredRecord(value) {
|
|
10169
|
-
if (isRecord(value)) {
|
|
10170
|
-
return value;
|
|
10171
|
-
}
|
|
10172
|
-
if (typeof value !== "string") {
|
|
10173
|
-
return void 0;
|
|
10174
|
-
}
|
|
10175
|
-
try {
|
|
10176
|
-
const parsed = JSON.parse(value);
|
|
10177
|
-
return isRecord(parsed) ? parsed : void 0;
|
|
10178
|
-
} catch {
|
|
10179
|
-
return void 0;
|
|
10180
|
-
}
|
|
10181
|
-
}
|
|
10182
|
-
function parseAgentTurnSessionRecord(value) {
|
|
10183
|
-
const parsed = parseStoredRecord(value);
|
|
10184
|
-
if (!parsed) {
|
|
10185
|
-
return void 0;
|
|
10186
|
-
}
|
|
10187
|
-
const status = parsed.state;
|
|
10188
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
10189
|
-
return void 0;
|
|
10190
|
-
}
|
|
10191
|
-
const conversationId = parsed.conversationId;
|
|
10192
|
-
const sessionId = parsed.sessionId;
|
|
10193
|
-
const sliceId = parsed.sliceId;
|
|
10194
|
-
const checkpointVersion = parsed.checkpointVersion;
|
|
10195
|
-
const updatedAtMs = parsed.updatedAtMs;
|
|
10196
|
-
const cumulativeDurationMs = toFiniteNonNegativeNumber(
|
|
10197
|
-
parsed.cumulativeDurationMs
|
|
10198
|
-
);
|
|
10199
|
-
const cumulativeUsage = parseAgentTurnUsage(parsed.cumulativeUsage);
|
|
10200
|
-
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
10201
|
-
return void 0;
|
|
10202
|
-
}
|
|
10203
|
-
const legacyPiMessages = Array.isArray(parsed.piMessages) ? parsed.piMessages : [];
|
|
10204
|
-
const messageCount = toFiniteNonNegativeNumber(parsed.messageCount) ?? legacyPiMessages.length;
|
|
10205
|
-
return {
|
|
10206
|
-
legacyPiMessages,
|
|
10207
|
-
record: {
|
|
10208
|
-
checkpointVersion,
|
|
10209
|
-
conversationId,
|
|
10210
|
-
sessionId,
|
|
10211
|
-
sliceId,
|
|
10212
|
-
state: status,
|
|
10213
|
-
updatedAtMs,
|
|
10214
|
-
messageCount,
|
|
10215
|
-
...cumulativeDurationMs !== void 0 ? { cumulativeDurationMs } : {},
|
|
10216
|
-
...cumulativeUsage ? { cumulativeUsage } : {},
|
|
10217
|
-
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
10218
|
-
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
10219
|
-
(value2) => typeof value2 === "string"
|
|
10220
|
-
)
|
|
10221
|
-
} : {},
|
|
10222
|
-
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
10223
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
10224
|
-
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
10225
|
-
}
|
|
10226
|
-
};
|
|
10227
|
-
}
|
|
10228
|
-
function materializePiMessages(legacyPiMessages, messageCount, sessionMessages) {
|
|
10229
|
-
if (messageCount === 0) {
|
|
10230
|
-
return [];
|
|
10231
|
-
}
|
|
10232
|
-
if (sessionMessages) {
|
|
10233
|
-
return sessionMessages;
|
|
10234
|
-
}
|
|
10235
|
-
if (legacyPiMessages.length >= messageCount) {
|
|
10236
|
-
return legacyPiMessages.slice(0, messageCount);
|
|
10237
|
-
}
|
|
10238
|
-
return void 0;
|
|
10239
|
-
}
|
|
10240
|
-
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
10241
|
-
const stateAdapter = getStateAdapter();
|
|
10242
|
-
await stateAdapter.connect();
|
|
10243
|
-
const value = await stateAdapter.get(
|
|
10244
|
-
agentTurnSessionKey(conversationId, sessionId)
|
|
10245
|
-
);
|
|
10246
|
-
const parsed = parseAgentTurnSessionRecord(value);
|
|
10247
|
-
if (!parsed) {
|
|
10248
|
-
return void 0;
|
|
10249
|
-
}
|
|
10250
|
-
const sessionMessages = await loadPiSessionMessages({
|
|
10251
|
-
conversationId,
|
|
10252
|
-
sessionId,
|
|
10253
|
-
messageCount: parsed.record.messageCount
|
|
10254
|
-
});
|
|
10255
|
-
const piMessages = materializePiMessages(
|
|
10256
|
-
parsed.legacyPiMessages,
|
|
10257
|
-
parsed.record.messageCount,
|
|
10258
|
-
sessionMessages
|
|
10259
|
-
);
|
|
10260
|
-
if (!piMessages) {
|
|
10261
|
-
return void 0;
|
|
10262
|
-
}
|
|
10263
|
-
return {
|
|
10264
|
-
...parsed.record,
|
|
10265
|
-
piMessages
|
|
10266
|
-
};
|
|
10267
|
-
}
|
|
10268
|
-
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
10269
|
-
const stateAdapter = getStateAdapter();
|
|
10270
|
-
await stateAdapter.connect();
|
|
10271
|
-
const existingValue = await stateAdapter.get(
|
|
10272
|
-
agentTurnSessionKey(args.conversationId, args.sessionId)
|
|
10273
|
-
);
|
|
10274
|
-
const existingRecord = parseAgentTurnSessionRecord(existingValue);
|
|
10275
|
-
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
10276
|
-
await commitPiSessionMessages({
|
|
10277
|
-
conversationId: args.conversationId,
|
|
10278
|
-
sessionId: args.sessionId,
|
|
10279
|
-
messages: args.piMessages,
|
|
10280
|
-
ttlMs
|
|
10281
|
-
});
|
|
10282
|
-
const storedMessageCount = args.piMessages.length;
|
|
10283
|
-
const checkpoint = {
|
|
10284
|
-
checkpointVersion: (existingRecord?.record.checkpointVersion ?? 0) + 1,
|
|
10285
|
-
conversationId: args.conversationId,
|
|
10286
|
-
sessionId: args.sessionId,
|
|
10287
|
-
sliceId: args.sliceId,
|
|
10288
|
-
state: args.state,
|
|
10289
|
-
updatedAtMs: Date.now(),
|
|
10290
|
-
messageCount: storedMessageCount,
|
|
10291
|
-
...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
|
|
10292
|
-
cumulativeDurationMs: Math.max(
|
|
10293
|
-
0,
|
|
10294
|
-
Math.floor(args.cumulativeDurationMs)
|
|
10295
|
-
)
|
|
10296
|
-
} : {},
|
|
10297
|
-
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
10298
|
-
...Array.isArray(args.loadedSkillNames) ? {
|
|
10299
|
-
loadedSkillNames: args.loadedSkillNames.filter(
|
|
10300
|
-
(value) => typeof value === "string"
|
|
10301
|
-
)
|
|
10302
|
-
} : {},
|
|
10303
|
-
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
10304
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
10305
|
-
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
10306
|
-
};
|
|
10307
|
-
await stateAdapter.set(
|
|
10308
|
-
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
10309
|
-
checkpoint,
|
|
10310
|
-
ttlMs
|
|
10311
|
-
);
|
|
10312
|
-
return {
|
|
10313
|
-
...checkpoint,
|
|
10314
|
-
piMessages: [...args.piMessages]
|
|
10315
|
-
};
|
|
10316
|
-
}
|
|
10317
|
-
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
10318
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
10319
|
-
args.conversationId,
|
|
10320
|
-
args.sessionId
|
|
10321
|
-
);
|
|
10322
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
10323
|
-
return void 0;
|
|
10324
|
-
}
|
|
10325
|
-
return await upsertAgentTurnSessionCheckpoint({
|
|
10326
|
-
conversationId: existing.conversationId,
|
|
10327
|
-
sessionId: existing.sessionId,
|
|
10328
|
-
sliceId: existing.sliceId,
|
|
10329
|
-
state: "superseded",
|
|
10330
|
-
piMessages: existing.piMessages,
|
|
10331
|
-
cumulativeDurationMs: existing.cumulativeDurationMs,
|
|
10332
|
-
cumulativeUsage: existing.cumulativeUsage,
|
|
10333
|
-
loadedSkillNames: existing.loadedSkillNames,
|
|
10334
|
-
resumeReason: existing.resumeReason,
|
|
10335
|
-
resumedFromSliceId: existing.resumedFromSliceId,
|
|
10336
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
10337
|
-
});
|
|
10338
|
-
}
|
|
10339
|
-
async function failAgentTurnSessionCheckpoint(args) {
|
|
10340
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
10341
|
-
args.conversationId,
|
|
10342
|
-
args.sessionId
|
|
10343
|
-
);
|
|
10344
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded" || typeof args.expectedCheckpointVersion === "number" && existing.checkpointVersion !== args.expectedCheckpointVersion) {
|
|
10345
|
-
return void 0;
|
|
10346
|
-
}
|
|
10347
|
-
return await upsertAgentTurnSessionCheckpoint({
|
|
10348
|
-
conversationId: existing.conversationId,
|
|
10349
|
-
sessionId: existing.sessionId,
|
|
10350
|
-
sliceId: existing.sliceId,
|
|
10351
|
-
state: "failed",
|
|
10352
|
-
piMessages: existing.piMessages,
|
|
10353
|
-
cumulativeDurationMs: existing.cumulativeDurationMs,
|
|
10354
|
-
cumulativeUsage: existing.cumulativeUsage,
|
|
10355
|
-
loadedSkillNames: existing.loadedSkillNames,
|
|
10356
|
-
resumeReason: existing.resumeReason,
|
|
10357
|
-
resumedFromSliceId: existing.resumedFromSliceId,
|
|
10358
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
10359
|
-
});
|
|
10360
|
-
}
|
|
10361
|
-
|
|
10362
10101
|
// src/chat/services/pending-auth.ts
|
|
10363
10102
|
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
10364
10103
|
function canReusePendingAuthLink(args) {
|
|
@@ -10391,10 +10130,10 @@ async function applyPendingAuthUpdate(args) {
|
|
|
10391
10130
|
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
10392
10131
|
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
10393
10132
|
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
10394
|
-
await
|
|
10133
|
+
await abandonAgentTurnSessionRecord({
|
|
10395
10134
|
conversationId: args.conversationId,
|
|
10396
10135
|
sessionId: previousPendingAuth.sessionId,
|
|
10397
|
-
errorMessage: "
|
|
10136
|
+
errorMessage: "Abandoned by a newer auth-blocked request in the same conversation."
|
|
10398
10137
|
});
|
|
10399
10138
|
}
|
|
10400
10139
|
}
|
|
@@ -10514,6 +10253,9 @@ function formatCommand(command) {
|
|
|
10514
10253
|
const collapsed = command.replace(/\s+/g, " ").trim();
|
|
10515
10254
|
return collapsed.length > 160 ? `${collapsed.slice(0, 157)}...` : collapsed;
|
|
10516
10255
|
}
|
|
10256
|
+
function authorizationId(args) {
|
|
10257
|
+
return `${args.sessionId}:${args.kind}:${args.provider}`;
|
|
10258
|
+
}
|
|
10517
10259
|
function buildCredentialFailureError(provider, command) {
|
|
10518
10260
|
const providerLabel = provider === "github" ? "GitHub" : formatProviderLabel(provider);
|
|
10519
10261
|
const plugin = getPluginDefinition(provider);
|
|
@@ -10576,6 +10318,21 @@ function createPluginAuthOrchestration(deps, abortAgent) {
|
|
|
10576
10318
|
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
10577
10319
|
});
|
|
10578
10320
|
}
|
|
10321
|
+
if (deps.conversationId && deps.sessionId) {
|
|
10322
|
+
await recordAuthorizationRequested({
|
|
10323
|
+
conversationId: deps.conversationId,
|
|
10324
|
+
kind: "plugin",
|
|
10325
|
+
provider,
|
|
10326
|
+
requesterId: deps.requesterId,
|
|
10327
|
+
authorizationId: authorizationId({
|
|
10328
|
+
kind: "plugin",
|
|
10329
|
+
provider,
|
|
10330
|
+
sessionId: deps.sessionId
|
|
10331
|
+
}),
|
|
10332
|
+
delivery: reusingPendingLink ? "private_link_reused" : "private_link_sent",
|
|
10333
|
+
ttlMs: THREAD_STATE_TTL_MS
|
|
10334
|
+
});
|
|
10335
|
+
}
|
|
10579
10336
|
pendingPause = new PluginAuthorizationPauseError(
|
|
10580
10337
|
provider,
|
|
10581
10338
|
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
@@ -10701,8 +10458,12 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
10701
10458
|
}
|
|
10702
10459
|
|
|
10703
10460
|
// src/chat/tools/agent-tools.ts
|
|
10704
|
-
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, pluginAuthOrchestration, onToolCall, agentHooks) {
|
|
10461
|
+
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, pluginAuthOrchestration, onToolCall, agentHooks, conversationPrivacy) {
|
|
10705
10462
|
const shouldTrace = shouldEmitDevAgentTrace();
|
|
10463
|
+
const effectiveConversationPrivacy = conversationPrivacy ?? "private";
|
|
10464
|
+
const serializeToolPayload = (payload) => serializeGenAiAttribute(
|
|
10465
|
+
effectiveConversationPrivacy === "private" ? toGenAiPayloadMetadata(payload) : payload
|
|
10466
|
+
);
|
|
10706
10467
|
return Object.entries(tools).map(([toolName, toolDef]) => ({
|
|
10707
10468
|
name: toolName,
|
|
10708
10469
|
label: toolName,
|
|
@@ -10712,7 +10473,11 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10712
10473
|
executionMode: toolDef.executionMode,
|
|
10713
10474
|
execute: async (toolCallId, params) => {
|
|
10714
10475
|
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
10715
|
-
const toolArgumentsAttribute =
|
|
10476
|
+
const toolArgumentsAttribute = serializeToolPayload(params);
|
|
10477
|
+
const toolArgumentsMetadata = toGenAiPayloadTraceAttributes(
|
|
10478
|
+
"app.ai.tool.call.arguments",
|
|
10479
|
+
params
|
|
10480
|
+
);
|
|
10716
10481
|
if (toolName === "reportProgress") {
|
|
10717
10482
|
const status = buildReportedProgressStatus(params);
|
|
10718
10483
|
if (status) {
|
|
@@ -10728,10 +10493,14 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10728
10493
|
try {
|
|
10729
10494
|
if (typeof toolDef.execute !== "function") {
|
|
10730
10495
|
const resultDetails = { ok: true };
|
|
10731
|
-
const toolResultAttribute2 =
|
|
10496
|
+
const toolResultAttribute2 = serializeToolPayload(resultDetails);
|
|
10732
10497
|
if (toolResultAttribute2) {
|
|
10733
10498
|
setSpanAttributes({
|
|
10734
|
-
"gen_ai.tool.call.result": toolResultAttribute2
|
|
10499
|
+
"gen_ai.tool.call.result": toolResultAttribute2,
|
|
10500
|
+
...toGenAiPayloadTraceAttributes(
|
|
10501
|
+
"app.ai.tool.call.result",
|
|
10502
|
+
resultDetails
|
|
10503
|
+
)
|
|
10735
10504
|
});
|
|
10736
10505
|
}
|
|
10737
10506
|
return {
|
|
@@ -10763,10 +10532,14 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10763
10532
|
});
|
|
10764
10533
|
}
|
|
10765
10534
|
const resultAttributeValue = normalized.details && typeof normalized.details === "object" && "rawResult" in normalized.details && normalized.details.rawResult !== void 0 ? normalized.details.rawResult : normalized.details;
|
|
10766
|
-
const toolResultAttribute =
|
|
10535
|
+
const toolResultAttribute = serializeToolPayload(resultAttributeValue);
|
|
10767
10536
|
if (toolResultAttribute) {
|
|
10768
10537
|
setSpanAttributes({
|
|
10769
|
-
"gen_ai.tool.call.result": toolResultAttribute
|
|
10538
|
+
"gen_ai.tool.call.result": toolResultAttribute,
|
|
10539
|
+
...toGenAiPayloadTraceAttributes(
|
|
10540
|
+
"app.ai.tool.call.result",
|
|
10541
|
+
resultAttributeValue
|
|
10542
|
+
)
|
|
10770
10543
|
});
|
|
10771
10544
|
}
|
|
10772
10545
|
return normalized;
|
|
@@ -10786,8 +10559,10 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10786
10559
|
{
|
|
10787
10560
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
10788
10561
|
"gen_ai.operation.name": "execute_tool",
|
|
10562
|
+
"app.conversation.privacy": effectiveConversationPrivacy,
|
|
10789
10563
|
"gen_ai.tool.name": toolName,
|
|
10790
10564
|
"gen_ai.tool.description": toolDef.description,
|
|
10565
|
+
...toolArgumentsMetadata,
|
|
10791
10566
|
...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
|
|
10792
10567
|
...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
|
|
10793
10568
|
}
|
|
@@ -10796,9 +10571,6 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10796
10571
|
}));
|
|
10797
10572
|
}
|
|
10798
10573
|
|
|
10799
|
-
// src/chat/runtime/thread-state.ts
|
|
10800
|
-
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
10801
|
-
|
|
10802
10574
|
// src/chat/configuration/validation.ts
|
|
10803
10575
|
var CONFIG_KEY_RE = /^[a-z0-9]+(?:\.[a-z0-9-]+)+$/;
|
|
10804
10576
|
var SECRET_KEY_RE = /(?:^|[_.-])(token|secret|password|passphrase|api[-_]?key|private[-_]?key|credential|auth)(?:$|[_.-])/i;
|
|
@@ -11144,7 +10916,6 @@ function coerceThreadConversationState(value) {
|
|
|
11144
10916
|
const processing = {
|
|
11145
10917
|
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
11146
10918
|
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
11147
|
-
lastSessionId: toOptionalString(rawProcessing.lastSessionId),
|
|
11148
10919
|
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
11149
10920
|
};
|
|
11150
10921
|
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
@@ -11224,6 +10995,7 @@ function coerceThreadArtifactsState(value) {
|
|
|
11224
10995
|
}
|
|
11225
10996
|
return {
|
|
11226
10997
|
assistantContextChannelId: typeof artifacts.assistantContextChannelId === "string" ? artifacts.assistantContextChannelId : void 0,
|
|
10998
|
+
assistantTitle: typeof artifacts.assistantTitle === "string" ? artifacts.assistantTitle : void 0,
|
|
11227
10999
|
assistantTitleSourceMessageId: typeof artifacts.assistantTitleSourceMessageId === "string" ? artifacts.assistantTitleSourceMessageId : void 0,
|
|
11228
11000
|
lastCanvasId: typeof artifacts.lastCanvasId === "string" ? artifacts.lastCanvasId : void 0,
|
|
11229
11001
|
lastCanvasUrl: typeof artifacts.lastCanvasUrl === "string" ? artifacts.lastCanvasUrl : void 0,
|
|
@@ -11322,7 +11094,11 @@ async function persistThreadStateById(threadId, patch) {
|
|
|
11322
11094
|
await stateAdapter.connect();
|
|
11323
11095
|
const key = threadStateKey(threadId);
|
|
11324
11096
|
const existing = await stateAdapter.get(key) ?? {};
|
|
11325
|
-
await stateAdapter.set(
|
|
11097
|
+
await stateAdapter.set(
|
|
11098
|
+
key,
|
|
11099
|
+
{ ...existing, ...payload },
|
|
11100
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
11101
|
+
);
|
|
11326
11102
|
}
|
|
11327
11103
|
function getChannelConfigurationService(thread) {
|
|
11328
11104
|
const channel = thread.channel;
|
|
@@ -11346,7 +11122,7 @@ function getChannelConfigurationServiceById(channelId) {
|
|
|
11346
11122
|
await stateAdapter.set(
|
|
11347
11123
|
key,
|
|
11348
11124
|
{ ...existing, configuration: state },
|
|
11349
|
-
|
|
11125
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
11350
11126
|
);
|
|
11351
11127
|
}
|
|
11352
11128
|
});
|
|
@@ -11389,7 +11165,6 @@ function markTurnClosed(args) {
|
|
|
11389
11165
|
}
|
|
11390
11166
|
function markTurnCompleted(args) {
|
|
11391
11167
|
clearActiveTurn(args.conversation, args.sessionId);
|
|
11392
|
-
args.conversation.processing.lastSessionId = args.sessionId;
|
|
11393
11168
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
11394
11169
|
args.updateConversationStats(args.conversation);
|
|
11395
11170
|
}
|
|
@@ -11931,8 +11706,8 @@ function addAgentTurnUsage(...usages) {
|
|
|
11931
11706
|
return hasAgentTurnUsage(components) ? components : void 0;
|
|
11932
11707
|
}
|
|
11933
11708
|
|
|
11934
|
-
// src/chat/services/turn-
|
|
11935
|
-
function
|
|
11709
|
+
// src/chat/services/turn-session-record.ts
|
|
11710
|
+
function logSessionRecordError(error, eventName, args, attributes, message) {
|
|
11936
11711
|
logException(
|
|
11937
11712
|
error,
|
|
11938
11713
|
eventName,
|
|
@@ -11965,171 +11740,201 @@ function isContinuableBoundary(messages) {
|
|
|
11965
11740
|
const lastRole = getPiMessageRole(messages.at(-1));
|
|
11966
11741
|
return lastRole === "user" || lastRole === "toolResult";
|
|
11967
11742
|
}
|
|
11968
|
-
|
|
11743
|
+
function resumableBoundary(messages, fallbackMessages) {
|
|
11744
|
+
const current = trimTrailingAssistantMessages(messages);
|
|
11745
|
+
if (current.length > 0 && isContinuableBoundary(current)) {
|
|
11746
|
+
return current;
|
|
11747
|
+
}
|
|
11748
|
+
return trimTrailingAssistantMessages(fallbackMessages ?? []);
|
|
11749
|
+
}
|
|
11750
|
+
async function loadTurnSessionRecord(ctx) {
|
|
11969
11751
|
const canUseTurnSession = Boolean(ctx.conversationId && ctx.sessionId);
|
|
11970
|
-
const
|
|
11971
|
-
const
|
|
11972
|
-
|
|
11752
|
+
const existingSessionRecord = canUseTurnSession && ctx.conversationId && ctx.sessionId ? await getAgentTurnSessionRecord(ctx.conversationId, ctx.sessionId) : void 0;
|
|
11753
|
+
const hasAwaitingResumeRecord = Boolean(
|
|
11754
|
+
existingSessionRecord && existingSessionRecord.state === "awaiting_resume" && existingSessionRecord.piMessages.length > 0
|
|
11973
11755
|
);
|
|
11974
11756
|
return {
|
|
11975
11757
|
canUseTurnSession,
|
|
11976
|
-
|
|
11977
|
-
currentSliceId:
|
|
11978
|
-
|
|
11758
|
+
resumedFromSessionRecord: hasAwaitingResumeRecord,
|
|
11759
|
+
currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
|
|
11760
|
+
existingSessionRecord
|
|
11979
11761
|
};
|
|
11980
11762
|
}
|
|
11981
|
-
async function
|
|
11763
|
+
async function persistRunningSessionRecord(args) {
|
|
11982
11764
|
if (args.messages.length === 0 || !isContinuableBoundary(args.messages)) {
|
|
11983
11765
|
return;
|
|
11984
11766
|
}
|
|
11985
11767
|
try {
|
|
11986
|
-
const
|
|
11768
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
11987
11769
|
args.conversationId,
|
|
11988
11770
|
args.sessionId
|
|
11989
11771
|
);
|
|
11990
|
-
await
|
|
11772
|
+
await upsertAgentTurnSessionRecord({
|
|
11773
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
11991
11774
|
conversationId: args.conversationId,
|
|
11992
|
-
cumulativeDurationMs:
|
|
11993
|
-
cumulativeUsage:
|
|
11775
|
+
cumulativeDurationMs: latestSessionRecord?.cumulativeDurationMs,
|
|
11776
|
+
cumulativeUsage: latestSessionRecord?.cumulativeUsage,
|
|
11994
11777
|
sessionId: args.sessionId,
|
|
11995
11778
|
sliceId: args.sliceId,
|
|
11996
11779
|
state: "running",
|
|
11997
11780
|
piMessages: args.messages,
|
|
11998
|
-
loadedSkillNames: args.loadedSkillNames
|
|
11781
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11782
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11783
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
11999
11784
|
});
|
|
12000
|
-
} catch (
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
"
|
|
11785
|
+
} catch (recordError) {
|
|
11786
|
+
logSessionRecordError(
|
|
11787
|
+
recordError,
|
|
11788
|
+
"agent_turn_running_session_record_failed",
|
|
12004
11789
|
args,
|
|
12005
11790
|
{
|
|
12006
11791
|
"app.ai.resume_slice_id": args.sliceId
|
|
12007
11792
|
},
|
|
12008
|
-
"Failed to persist running turn
|
|
11793
|
+
"Failed to persist running turn session record"
|
|
12009
11794
|
);
|
|
12010
11795
|
}
|
|
12011
11796
|
}
|
|
12012
|
-
async function
|
|
11797
|
+
async function persistCompletedSessionRecord(args) {
|
|
12013
11798
|
try {
|
|
12014
|
-
const
|
|
11799
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
12015
11800
|
args.conversationId,
|
|
12016
11801
|
args.sessionId
|
|
12017
11802
|
);
|
|
12018
|
-
await
|
|
11803
|
+
await upsertAgentTurnSessionRecord({
|
|
11804
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
12019
11805
|
conversationId: args.conversationId,
|
|
12020
11806
|
cumulativeDurationMs: addDurationMs(
|
|
12021
|
-
|
|
11807
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
12022
11808
|
args.currentDurationMs
|
|
12023
11809
|
),
|
|
12024
11810
|
cumulativeUsage: addAgentTurnUsage(
|
|
12025
|
-
|
|
11811
|
+
latestSessionRecord?.cumulativeUsage,
|
|
12026
11812
|
args.currentUsage
|
|
12027
11813
|
),
|
|
12028
11814
|
sessionId: args.sessionId,
|
|
12029
11815
|
sliceId: args.sliceId,
|
|
12030
11816
|
state: "completed",
|
|
12031
11817
|
piMessages: args.allMessages,
|
|
12032
|
-
loadedSkillNames: args.loadedSkillNames
|
|
11818
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11819
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11820
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
12033
11821
|
});
|
|
12034
|
-
} catch (
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
"
|
|
11822
|
+
} catch (recordError) {
|
|
11823
|
+
logSessionRecordError(
|
|
11824
|
+
recordError,
|
|
11825
|
+
"agent_turn_completed_session_record_failed",
|
|
12038
11826
|
args,
|
|
12039
11827
|
{
|
|
12040
11828
|
"app.ai.resume_slice_id": args.sliceId
|
|
12041
11829
|
},
|
|
12042
|
-
"Failed to persist completed turn
|
|
11830
|
+
"Failed to persist completed turn session record"
|
|
12043
11831
|
);
|
|
12044
11832
|
}
|
|
12045
11833
|
}
|
|
12046
|
-
async function
|
|
11834
|
+
async function persistAuthPauseSessionRecord(args) {
|
|
12047
11835
|
const nextSliceId = args.currentSliceId + 1;
|
|
12048
11836
|
try {
|
|
12049
|
-
const
|
|
11837
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
12050
11838
|
args.conversationId,
|
|
12051
11839
|
args.sessionId
|
|
12052
11840
|
);
|
|
12053
|
-
const piMessages =
|
|
12054
|
-
args.messages
|
|
11841
|
+
const piMessages = resumableBoundary(
|
|
11842
|
+
args.messages,
|
|
11843
|
+
latestSessionRecord?.piMessages
|
|
12055
11844
|
);
|
|
12056
|
-
|
|
11845
|
+
if (piMessages.length === 0 || !isContinuableBoundary(piMessages)) {
|
|
11846
|
+
return void 0;
|
|
11847
|
+
}
|
|
11848
|
+
return await upsertAgentTurnSessionRecord({
|
|
11849
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
12057
11850
|
conversationId: args.conversationId,
|
|
12058
11851
|
cumulativeDurationMs: addDurationMs(
|
|
12059
|
-
|
|
11852
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
12060
11853
|
args.currentDurationMs
|
|
12061
11854
|
),
|
|
12062
11855
|
cumulativeUsage: addAgentTurnUsage(
|
|
12063
|
-
|
|
11856
|
+
latestSessionRecord?.cumulativeUsage,
|
|
12064
11857
|
args.currentUsage
|
|
12065
11858
|
),
|
|
12066
11859
|
sessionId: args.sessionId,
|
|
12067
11860
|
sliceId: nextSliceId,
|
|
12068
11861
|
state: "awaiting_resume",
|
|
12069
11862
|
piMessages,
|
|
12070
|
-
loadedSkillNames: args.loadedSkillNames,
|
|
11863
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
12071
11864
|
resumeReason: "auth",
|
|
12072
11865
|
resumedFromSliceId: args.currentSliceId,
|
|
12073
|
-
errorMessage: args.errorMessage
|
|
11866
|
+
errorMessage: args.errorMessage,
|
|
11867
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11868
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
12074
11869
|
});
|
|
12075
|
-
} catch (
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
"
|
|
11870
|
+
} catch (recordError) {
|
|
11871
|
+
logSessionRecordError(
|
|
11872
|
+
recordError,
|
|
11873
|
+
"agent_turn_auth_resume_session_record_failed",
|
|
12079
11874
|
args,
|
|
12080
11875
|
{
|
|
12081
11876
|
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
12082
11877
|
"app.ai.resume_next_slice_id": nextSliceId
|
|
12083
11878
|
},
|
|
12084
|
-
"Failed to persist auth
|
|
11879
|
+
"Failed to persist auth session record before retry"
|
|
12085
11880
|
);
|
|
12086
11881
|
}
|
|
12087
11882
|
return void 0;
|
|
12088
11883
|
}
|
|
12089
|
-
async function
|
|
11884
|
+
async function persistTimeoutSessionRecord(args) {
|
|
12090
11885
|
const nextSliceId = args.currentSliceId + 1;
|
|
12091
11886
|
try {
|
|
12092
|
-
const
|
|
11887
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
12093
11888
|
args.conversationId,
|
|
12094
11889
|
args.sessionId
|
|
12095
11890
|
);
|
|
12096
|
-
const piMessages =
|
|
12097
|
-
args.messages
|
|
11891
|
+
const piMessages = resumableBoundary(
|
|
11892
|
+
args.messages,
|
|
11893
|
+
latestSessionRecord?.piMessages
|
|
12098
11894
|
);
|
|
12099
|
-
|
|
11895
|
+
if (piMessages.length === 0 || !isContinuableBoundary(piMessages)) {
|
|
11896
|
+
return void 0;
|
|
11897
|
+
}
|
|
11898
|
+
return await upsertAgentTurnSessionRecord({
|
|
11899
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
12100
11900
|
conversationId: args.conversationId,
|
|
12101
11901
|
cumulativeDurationMs: addDurationMs(
|
|
12102
|
-
|
|
11902
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
12103
11903
|
args.currentDurationMs
|
|
12104
11904
|
),
|
|
12105
11905
|
cumulativeUsage: addAgentTurnUsage(
|
|
12106
|
-
|
|
11906
|
+
latestSessionRecord?.cumulativeUsage,
|
|
12107
11907
|
args.currentUsage
|
|
12108
11908
|
),
|
|
12109
11909
|
sessionId: args.sessionId,
|
|
12110
11910
|
sliceId: nextSliceId,
|
|
12111
11911
|
state: "awaiting_resume",
|
|
12112
11912
|
piMessages,
|
|
12113
|
-
loadedSkillNames: args.loadedSkillNames,
|
|
11913
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
12114
11914
|
resumeReason: "timeout",
|
|
12115
11915
|
resumedFromSliceId: args.currentSliceId,
|
|
12116
|
-
errorMessage: args.errorMessage
|
|
11916
|
+
errorMessage: args.errorMessage,
|
|
11917
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11918
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
12117
11919
|
});
|
|
12118
|
-
} catch (
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
"
|
|
11920
|
+
} catch (recordError) {
|
|
11921
|
+
logSessionRecordError(
|
|
11922
|
+
recordError,
|
|
11923
|
+
"agent_turn_timeout_resume_session_record_failed",
|
|
12122
11924
|
args,
|
|
12123
11925
|
{
|
|
12124
11926
|
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
12125
11927
|
"app.ai.resume_next_slice_id": nextSliceId
|
|
12126
11928
|
},
|
|
12127
|
-
"Failed to persist timeout
|
|
11929
|
+
"Failed to persist timeout session record before scheduling resume"
|
|
12128
11930
|
);
|
|
12129
11931
|
return void 0;
|
|
12130
11932
|
}
|
|
12131
11933
|
}
|
|
12132
11934
|
|
|
11935
|
+
// src/chat/services/mcp-auth-orchestration.ts
|
|
11936
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
|
|
11937
|
+
|
|
12133
11938
|
// src/chat/mcp/oauth.ts
|
|
12134
11939
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12135
11940
|
import { StreamableHTTPClientTransport as StreamableHTTPClientTransport2 } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -12397,6 +12202,9 @@ var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
|
12397
12202
|
super("mcp", provider, disposition);
|
|
12398
12203
|
}
|
|
12399
12204
|
};
|
|
12205
|
+
function authorizationId2(args) {
|
|
12206
|
+
return `${args.sessionId}:${args.kind}:${args.provider}`;
|
|
12207
|
+
}
|
|
12400
12208
|
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
12401
12209
|
let pendingPause;
|
|
12402
12210
|
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
@@ -12473,6 +12281,21 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
12473
12281
|
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
12474
12282
|
});
|
|
12475
12283
|
}
|
|
12284
|
+
if (deps.conversationId && deps.sessionId && deps.requesterId) {
|
|
12285
|
+
await recordAuthorizationRequested({
|
|
12286
|
+
conversationId: deps.conversationId,
|
|
12287
|
+
kind: "mcp",
|
|
12288
|
+
provider,
|
|
12289
|
+
requesterId: deps.requesterId,
|
|
12290
|
+
authorizationId: authorizationId2({
|
|
12291
|
+
kind: "mcp",
|
|
12292
|
+
provider,
|
|
12293
|
+
sessionId: deps.sessionId
|
|
12294
|
+
}),
|
|
12295
|
+
delivery: reusingPendingLink ? "private_link_reused" : "private_link_sent",
|
|
12296
|
+
ttlMs: THREAD_STATE_TTL_MS2
|
|
12297
|
+
});
|
|
12298
|
+
}
|
|
12476
12299
|
pendingPause = new McpAuthorizationPauseError(
|
|
12477
12300
|
provider,
|
|
12478
12301
|
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
@@ -12517,6 +12340,15 @@ function extractSliceUsage(messages, beforeMessageCount) {
|
|
|
12517
12340
|
);
|
|
12518
12341
|
return hasAgentTurnUsage(usage) ? usage : void 0;
|
|
12519
12342
|
}
|
|
12343
|
+
function requesterFromContext(requester, requesterId) {
|
|
12344
|
+
const identity = {
|
|
12345
|
+
...requester?.email ? { email: requester.email } : {},
|
|
12346
|
+
...requester?.fullName ? { fullName: requester.fullName } : {},
|
|
12347
|
+
...requesterId ?? requester?.userId ? { slackUserId: requesterId ?? requester?.userId } : {},
|
|
12348
|
+
...requester?.userName ? { slackUserName: requester.userName } : {}
|
|
12349
|
+
};
|
|
12350
|
+
return Object.keys(identity).length > 0 ? identity : void 0;
|
|
12351
|
+
}
|
|
12520
12352
|
function supportsRouterTextPreview(mediaType) {
|
|
12521
12353
|
const baseMediaType = mediaType.split(";", 1)[0]?.trim().toLowerCase();
|
|
12522
12354
|
if (!baseMediaType) {
|
|
@@ -12604,11 +12436,21 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12604
12436
|
let lastKnownSandboxDependencyProfileHash = context.sandbox?.sandboxDependencyProfileHash;
|
|
12605
12437
|
let loadedSkillNamesForResume = [];
|
|
12606
12438
|
let mcpToolManager;
|
|
12439
|
+
let connectedMcpProviders = /* @__PURE__ */ new Set();
|
|
12440
|
+
let canRecordMcpProviders = false;
|
|
12607
12441
|
let sandboxExecutor;
|
|
12608
12442
|
let timedOut = false;
|
|
12609
12443
|
let turnUsage;
|
|
12610
12444
|
let thinkingSelection;
|
|
12611
|
-
const
|
|
12445
|
+
const requester = requesterFromContext(
|
|
12446
|
+
context.requester,
|
|
12447
|
+
context.correlation?.requesterId
|
|
12448
|
+
);
|
|
12449
|
+
const conversationPrivacy = resolveConversationPrivacy({
|
|
12450
|
+
channelId: context.correlation?.channelId,
|
|
12451
|
+
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId
|
|
12452
|
+
});
|
|
12453
|
+
const sessionRecordLogContext = {
|
|
12612
12454
|
threadId: context.correlation?.threadId,
|
|
12613
12455
|
requesterId: context.correlation?.requesterId,
|
|
12614
12456
|
channelId: context.correlation?.channelId,
|
|
@@ -12618,6 +12460,25 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12618
12460
|
assistantUserName: botConfig.userName,
|
|
12619
12461
|
modelId: botConfig.modelId
|
|
12620
12462
|
};
|
|
12463
|
+
const recordConnectedMcpProvider = async (provider) => {
|
|
12464
|
+
if (!canRecordMcpProviders || !timeoutResumeConversationId || connectedMcpProviders.has(provider)) {
|
|
12465
|
+
return;
|
|
12466
|
+
}
|
|
12467
|
+
await recordMcpProviderConnected({
|
|
12468
|
+
conversationId: timeoutResumeConversationId,
|
|
12469
|
+
provider,
|
|
12470
|
+
ttlMs: THREAD_STATE_TTL_MS3
|
|
12471
|
+
});
|
|
12472
|
+
connectedMcpProviders.add(provider);
|
|
12473
|
+
};
|
|
12474
|
+
const recordActiveMcpProviders = async () => {
|
|
12475
|
+
if (!mcpToolManager) {
|
|
12476
|
+
return;
|
|
12477
|
+
}
|
|
12478
|
+
for (const provider of mcpToolManager.getActiveProviders()) {
|
|
12479
|
+
await recordConnectedMcpProvider(provider);
|
|
12480
|
+
}
|
|
12481
|
+
};
|
|
12621
12482
|
const getSandboxMetadata = () => sandboxExecutor ? {
|
|
12622
12483
|
sandboxId: sandboxExecutor.getSandboxId(),
|
|
12623
12484
|
sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash()
|
|
@@ -12685,16 +12546,22 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12685
12546
|
const skillInvocation = parseSkillInvocation(userInput, availableSkills);
|
|
12686
12547
|
const invokedSkill = skillInvocation ? findSkillByName(skillInvocation.skillName, availableSkills) : null;
|
|
12687
12548
|
const activeSkills = [];
|
|
12549
|
+
const syncLoadedSkillNamesForResume = () => {
|
|
12550
|
+
loadedSkillNamesForResume = activeSkills.map((skill) => skill.name);
|
|
12551
|
+
};
|
|
12688
12552
|
const skillSandbox = new SkillSandbox(availableSkills, activeSkills);
|
|
12689
12553
|
const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(context);
|
|
12690
|
-
const
|
|
12554
|
+
const turnSessionState = await loadTurnSessionRecord({
|
|
12691
12555
|
conversationId: sessionConversationId,
|
|
12692
12556
|
sessionId
|
|
12693
12557
|
});
|
|
12694
|
-
const {
|
|
12558
|
+
const { resumedFromSessionRecord, currentSliceId, existingSessionRecord } = turnSessionState;
|
|
12695
12559
|
timeoutResumeConversationId = sessionConversationId;
|
|
12696
12560
|
timeoutResumeSessionId = sessionId;
|
|
12697
12561
|
timeoutResumeSliceId = currentSliceId;
|
|
12562
|
+
canRecordMcpProviders = Boolean(
|
|
12563
|
+
turnSessionState.canUseTurnSession && sessionConversationId && sessionId
|
|
12564
|
+
);
|
|
12698
12565
|
const persistedConfigurationValues = context.channelConfiguration ? await context.channelConfiguration.resolveValues() : {};
|
|
12699
12566
|
configurationValues = {
|
|
12700
12567
|
...getConfigDefaults(),
|
|
@@ -12738,6 +12605,12 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12738
12605
|
const currentSandboxExecutor = sandboxExecutor;
|
|
12739
12606
|
sandboxExecutor.configureSkills(availableSkills);
|
|
12740
12607
|
sandboxExecutor.configureReferenceFiles(listReferenceFiles());
|
|
12608
|
+
const priorPiMessages = resumedFromSessionRecord ? existingSessionRecord?.piMessages : context.piMessages;
|
|
12609
|
+
connectedMcpProviders = new Set(
|
|
12610
|
+
turnSessionState.canUseTurnSession && sessionConversationId ? await loadConnectedMcpProviders({
|
|
12611
|
+
conversationId: sessionConversationId
|
|
12612
|
+
}) : []
|
|
12613
|
+
);
|
|
12741
12614
|
let sandboxPromise;
|
|
12742
12615
|
let sandboxPromiseId;
|
|
12743
12616
|
const clearSandboxPromise = () => {
|
|
@@ -12782,16 +12655,20 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12782
12655
|
cwd: input.cwd
|
|
12783
12656
|
})).runCommand(input)
|
|
12784
12657
|
};
|
|
12785
|
-
for (const skillName of
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12658
|
+
for (const skillName of inferLoadedSkillNamesFromPiMessages(
|
|
12659
|
+
priorPiMessages
|
|
12660
|
+
)) {
|
|
12661
|
+
const restoredSkill = await skillSandbox.loadSkill(skillName);
|
|
12662
|
+
if (restoredSkill) {
|
|
12663
|
+
upsertActiveSkill(activeSkills, restoredSkill);
|
|
12664
|
+
syncLoadedSkillNamesForResume();
|
|
12789
12665
|
}
|
|
12790
12666
|
}
|
|
12791
12667
|
if (invokedSkill) {
|
|
12792
|
-
const
|
|
12793
|
-
if (
|
|
12794
|
-
upsertActiveSkill(activeSkills,
|
|
12668
|
+
const restoredSkill = await skillSandbox.loadSkill(invokedSkill.name);
|
|
12669
|
+
if (restoredSkill) {
|
|
12670
|
+
upsertActiveSkill(activeSkills, restoredSkill);
|
|
12671
|
+
syncLoadedSkillNamesForResume();
|
|
12795
12672
|
}
|
|
12796
12673
|
}
|
|
12797
12674
|
const promptConversationContext = context.piMessages && context.piMessages.length > 0 ? void 0 : context.conversationContext;
|
|
@@ -12804,6 +12681,14 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12804
12681
|
userAttachments: context.userAttachments,
|
|
12805
12682
|
userTurnText
|
|
12806
12683
|
});
|
|
12684
|
+
const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [
|
|
12685
|
+
...context.piMessages ?? [],
|
|
12686
|
+
{
|
|
12687
|
+
role: "user",
|
|
12688
|
+
content: userContentParts,
|
|
12689
|
+
timestamp: Date.now()
|
|
12690
|
+
}
|
|
12691
|
+
];
|
|
12807
12692
|
thinkingSelection = await selectTurnThinkingLevel({
|
|
12808
12693
|
completeObject,
|
|
12809
12694
|
conversationContext: context.conversationContext,
|
|
@@ -12872,9 +12757,6 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12872
12757
|
});
|
|
12873
12758
|
const turnMcpToolManager = mcpToolManager;
|
|
12874
12759
|
const getPendingAuthPause = () => pluginAuth.getPendingPause() ?? mcpAuth.getPendingPause();
|
|
12875
|
-
const syncResumeState = () => {
|
|
12876
|
-
loadedSkillNamesForResume = activeSkills.map((skill) => skill.name);
|
|
12877
|
-
};
|
|
12878
12760
|
setTags({
|
|
12879
12761
|
conversationId: spanContext.conversationId,
|
|
12880
12762
|
slackThreadId: context.correlation?.threadId,
|
|
@@ -12915,9 +12797,10 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12915
12797
|
const resolvedSkill = await skillSandbox.loadSkill(loadedSkill.name);
|
|
12916
12798
|
const effective = resolvedSkill ?? loadedSkill;
|
|
12917
12799
|
upsertActiveSkill(activeSkills, effective);
|
|
12918
|
-
|
|
12919
|
-
await turnMcpToolManager.activateForSkill(effective)
|
|
12920
|
-
|
|
12800
|
+
syncLoadedSkillNamesForResume();
|
|
12801
|
+
if (await turnMcpToolManager.activateForSkill(effective)) {
|
|
12802
|
+
await recordConnectedMcpProvider(effective.pluginProvider);
|
|
12803
|
+
}
|
|
12921
12804
|
if (mcpAuth.getPendingPause()) {
|
|
12922
12805
|
return void 0;
|
|
12923
12806
|
}
|
|
@@ -12927,12 +12810,9 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12927
12810
|
if (!turnMcpToolManager.getActiveProviders().includes(effective.pluginProvider)) {
|
|
12928
12811
|
return void 0;
|
|
12929
12812
|
}
|
|
12930
|
-
const availableToolCount = turnMcpToolManager.getActiveToolCatalog(
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
provider: effective.pluginProvider
|
|
12934
|
-
}
|
|
12935
|
-
).length;
|
|
12813
|
+
const availableToolCount = turnMcpToolManager.getActiveToolCatalog({
|
|
12814
|
+
provider: effective.pluginProvider
|
|
12815
|
+
}).length;
|
|
12936
12816
|
return {
|
|
12937
12817
|
mcp_provider: effective.pluginProvider,
|
|
12938
12818
|
available_tool_count: availableToolCount
|
|
@@ -12949,15 +12829,15 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12949
12829
|
userText: userInput,
|
|
12950
12830
|
artifactState: context.artifactState,
|
|
12951
12831
|
configuration: configurationValues,
|
|
12952
|
-
getActiveSkills: () => activeSkills,
|
|
12953
12832
|
mcpToolManager: turnMcpToolManager,
|
|
12954
12833
|
sandbox,
|
|
12955
12834
|
advisor: {
|
|
12956
12835
|
config: botConfig.advisor,
|
|
12957
12836
|
conversationId: sessionConversationId,
|
|
12837
|
+
conversationPrivacy,
|
|
12958
12838
|
logContext: spanContext,
|
|
12959
12839
|
getTools: () => advisorTools,
|
|
12960
|
-
streamFn: createTracedStreamFn()
|
|
12840
|
+
streamFn: createTracedStreamFn({ conversationPrivacy })
|
|
12961
12841
|
}
|
|
12962
12842
|
}
|
|
12963
12843
|
);
|
|
@@ -12968,24 +12848,37 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12968
12848
|
promptGuidelines: definition.promptGuidelines,
|
|
12969
12849
|
promptSnippet: definition.promptSnippet
|
|
12970
12850
|
}));
|
|
12971
|
-
|
|
12851
|
+
const providersToRestore = /* @__PURE__ */ new Set([
|
|
12852
|
+
...connectedMcpProviders,
|
|
12853
|
+
...inferActiveMcpProvidersFromPiMessages(priorPiMessages)
|
|
12854
|
+
]);
|
|
12855
|
+
for (const provider of providersToRestore) {
|
|
12856
|
+
if (await turnMcpToolManager.activateProvider(provider)) {
|
|
12857
|
+
await recordConnectedMcpProvider(provider);
|
|
12858
|
+
}
|
|
12859
|
+
if (mcpAuth.getPendingPause()) {
|
|
12860
|
+
timeoutResumeMessages = preAgentPromptMessages();
|
|
12861
|
+
throw mcpAuth.getPendingPause();
|
|
12862
|
+
}
|
|
12863
|
+
}
|
|
12972
12864
|
for (const skill of activeSkills) {
|
|
12973
|
-
await turnMcpToolManager.activateForSkill(skill)
|
|
12974
|
-
|
|
12865
|
+
if (await turnMcpToolManager.activateForSkill(skill)) {
|
|
12866
|
+
await recordConnectedMcpProvider(skill.pluginProvider);
|
|
12867
|
+
}
|
|
12975
12868
|
if (mcpAuth.getPendingPause()) {
|
|
12976
|
-
timeoutResumeMessages =
|
|
12869
|
+
timeoutResumeMessages = preAgentPromptMessages();
|
|
12977
12870
|
throw mcpAuth.getPendingPause();
|
|
12978
12871
|
}
|
|
12979
12872
|
}
|
|
12980
|
-
syncResumeState();
|
|
12981
12873
|
const activeMcpCatalogs = toActiveMcpCatalogSummaries(
|
|
12982
|
-
turnMcpToolManager.getActiveToolCatalog(
|
|
12874
|
+
turnMcpToolManager.getActiveToolCatalog()
|
|
12983
12875
|
);
|
|
12984
12876
|
baseInstructions = buildSystemPrompt();
|
|
12877
|
+
const includeSessionContext = resumedFromSessionRecord || !hasRuntimeTurnContext(priorPiMessages ?? []);
|
|
12985
12878
|
const turnContextPrompt = buildTurnContextPrompt({
|
|
12986
12879
|
availableSkills,
|
|
12987
|
-
activeSkills,
|
|
12988
12880
|
activeMcpCatalogs,
|
|
12881
|
+
includeSessionContext,
|
|
12989
12882
|
toolGuidance,
|
|
12990
12883
|
runtime: {
|
|
12991
12884
|
conversationId: spanContext.conversationId,
|
|
@@ -12994,14 +12887,14 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12994
12887
|
invocation: skillInvocation,
|
|
12995
12888
|
requester: context.requester,
|
|
12996
12889
|
artifactState: context.artifactState,
|
|
12997
|
-
configuration: configurationValues
|
|
12998
|
-
turnState: resumedFromCheckpoint ? "resumed" : "fresh"
|
|
12890
|
+
configuration: configurationValues
|
|
12999
12891
|
});
|
|
12892
|
+
const turnContextParts = turnContextPrompt ? [{ type: "text", text: turnContextPrompt }] : [];
|
|
13000
12893
|
const promptContentParts = [
|
|
13001
|
-
|
|
12894
|
+
...turnContextParts,
|
|
13002
12895
|
...userContentParts
|
|
13003
12896
|
];
|
|
13004
|
-
const
|
|
12897
|
+
const inputMessages = [
|
|
13005
12898
|
{
|
|
13006
12899
|
role: "system",
|
|
13007
12900
|
content: [{ type: "text", text: baseInstructions }]
|
|
@@ -13010,7 +12903,10 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13010
12903
|
role: "user",
|
|
13011
12904
|
content: promptContentParts.map((part) => toObservablePromptPart(part))
|
|
13012
12905
|
}
|
|
13013
|
-
]
|
|
12906
|
+
];
|
|
12907
|
+
const inputMessagesAttribute = serializeGenAiAttribute(
|
|
12908
|
+
conversationPrivacy !== "public" ? inputMessages.map(toGenAiMessageMetadata) : inputMessages
|
|
12909
|
+
);
|
|
13014
12910
|
const onToolCall = (toolName, params) => {
|
|
13015
12911
|
toolCalls.push(toolName);
|
|
13016
12912
|
try {
|
|
@@ -13035,7 +12931,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13035
12931
|
sandboxExecutor,
|
|
13036
12932
|
pluginAuth,
|
|
13037
12933
|
onToolCall,
|
|
13038
|
-
agentPluginHooks
|
|
12934
|
+
agentPluginHooks,
|
|
12935
|
+
conversationPrivacy
|
|
13039
12936
|
);
|
|
13040
12937
|
advisorTools = createAgentTools(
|
|
13041
12938
|
createAdvisorToolDefinitions(tools),
|
|
@@ -13045,11 +12942,12 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13045
12942
|
sandboxExecutor,
|
|
13046
12943
|
pluginAuth,
|
|
13047
12944
|
onToolCall,
|
|
13048
|
-
agentPluginHooks
|
|
12945
|
+
agentPluginHooks,
|
|
12946
|
+
conversationPrivacy
|
|
13049
12947
|
);
|
|
13050
12948
|
agent = new Agent2({
|
|
13051
12949
|
getApiKey: () => getPiGatewayApiKeyOverride(),
|
|
13052
|
-
streamFn: createTracedStreamFn(),
|
|
12950
|
+
streamFn: createTracedStreamFn({ conversationPrivacy }),
|
|
13053
12951
|
initialState: {
|
|
13054
12952
|
systemPrompt: baseInstructions,
|
|
13055
12953
|
model: resolveGatewayModel(botConfig.modelId),
|
|
@@ -13060,16 +12958,18 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13060
12958
|
let hasEmittedText = false;
|
|
13061
12959
|
let needsSeparator = false;
|
|
13062
12960
|
const persistSafeBoundary = async (messages) => {
|
|
13063
|
-
if (!
|
|
12961
|
+
if (!turnSessionState.canUseTurnSession || !sessionConversationId || !sessionId) {
|
|
13064
12962
|
return;
|
|
13065
12963
|
}
|
|
13066
|
-
await
|
|
12964
|
+
await persistRunningSessionRecord({
|
|
12965
|
+
channelName: context.correlation?.channelName,
|
|
13067
12966
|
conversationId: sessionConversationId,
|
|
13068
12967
|
sessionId,
|
|
13069
12968
|
sliceId: currentSliceId,
|
|
13070
12969
|
messages,
|
|
13071
12970
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
13072
|
-
logContext:
|
|
12971
|
+
logContext: sessionRecordLogContext,
|
|
12972
|
+
requester
|
|
13073
12973
|
});
|
|
13074
12974
|
};
|
|
13075
12975
|
const unsubscribe = agent.subscribe((event) => {
|
|
@@ -13113,17 +13013,17 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13113
13013
|
let newMessages = [];
|
|
13114
13014
|
beforeMessageCount = agent.state.messages.length;
|
|
13115
13015
|
try {
|
|
13116
|
-
if (
|
|
13117
|
-
agent.state.messages = refreshRuntimeTurnContext(
|
|
13118
|
-
|
|
13016
|
+
if (resumedFromSessionRecord) {
|
|
13017
|
+
agent.state.messages = turnContextPrompt ? refreshRuntimeTurnContext(
|
|
13018
|
+
existingSessionRecord.piMessages,
|
|
13119
13019
|
turnContextPrompt
|
|
13120
|
-
);
|
|
13020
|
+
) : existingSessionRecord.piMessages;
|
|
13121
13021
|
} else if (context.piMessages && context.piMessages.length > 0) {
|
|
13122
13022
|
agent.state.messages = [...context.piMessages];
|
|
13123
13023
|
}
|
|
13124
13024
|
beforeMessageCount = agent.state.messages.length;
|
|
13125
13025
|
await withSpan(
|
|
13126
|
-
|
|
13026
|
+
`invoke_agent ${botConfig.modelId}`,
|
|
13127
13027
|
"gen_ai.invoke_agent",
|
|
13128
13028
|
spanContext,
|
|
13129
13029
|
async () => {
|
|
@@ -13133,7 +13033,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13133
13033
|
content: promptContentParts,
|
|
13134
13034
|
timestamp: Date.now()
|
|
13135
13035
|
};
|
|
13136
|
-
if (!
|
|
13036
|
+
if (!resumedFromSessionRecord) {
|
|
13137
13037
|
await persistSafeBoundary([
|
|
13138
13038
|
...agent.state.messages,
|
|
13139
13039
|
freshPromptMessage
|
|
@@ -13185,13 +13085,15 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13185
13085
|
}
|
|
13186
13086
|
}
|
|
13187
13087
|
};
|
|
13188
|
-
let run =
|
|
13088
|
+
let run = resumedFromSessionRecord ? agent.continue() : agent.prompt(freshPromptMessage);
|
|
13189
13089
|
let retryUsage;
|
|
13190
13090
|
for (let attempt = 0; ; attempt += 1) {
|
|
13191
13091
|
promptResult = await runAgentStep(run);
|
|
13192
13092
|
newMessages = agent.state.messages.slice(beforeMessageCount);
|
|
13193
13093
|
const outputMessages = newMessages.filter(isAssistantMessage);
|
|
13194
|
-
const outputMessagesAttribute = serializeGenAiAttribute(
|
|
13094
|
+
const outputMessagesAttribute = serializeGenAiAttribute(
|
|
13095
|
+
conversationPrivacy !== "public" ? outputMessages.map(toGenAiMessageMetadata) : outputMessages
|
|
13096
|
+
);
|
|
13195
13097
|
const usageSummary = extractGenAiUsageSummary(
|
|
13196
13098
|
promptResult,
|
|
13197
13099
|
agent.state,
|
|
@@ -13201,6 +13103,10 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13201
13103
|
turnUsage = addAgentTurnUsage(retryUsage, currentUsage);
|
|
13202
13104
|
setSpanAttributes({
|
|
13203
13105
|
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
13106
|
+
...toGenAiMessagesTraceAttributes(
|
|
13107
|
+
"app.ai.output",
|
|
13108
|
+
outputMessages
|
|
13109
|
+
),
|
|
13204
13110
|
...extractGenAiUsageAttributes(usageSummary)
|
|
13205
13111
|
});
|
|
13206
13112
|
if (getPendingAuthPause()) {
|
|
@@ -13235,23 +13141,34 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13235
13141
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
13236
13142
|
"gen_ai.operation.name": "invoke_agent",
|
|
13237
13143
|
"gen_ai.request.model": botConfig.modelId,
|
|
13144
|
+
"gen_ai.output.type": "text",
|
|
13145
|
+
"server.address": GEN_AI_SERVER_ADDRESS,
|
|
13146
|
+
"server.port": GEN_AI_SERVER_PORT,
|
|
13147
|
+
...conversationPrivacy ? { "app.conversation.privacy": conversationPrivacy } : {},
|
|
13148
|
+
...sessionConversationId ? { "app.ai.session.conversation_id": sessionConversationId } : {},
|
|
13149
|
+
...sessionId ? { "app.ai.turn.session_id": sessionId } : {},
|
|
13150
|
+
...timeoutResumeSliceId ? { "app.ai.turn.slice_id": timeoutResumeSliceId } : {},
|
|
13238
13151
|
"app.ai.reasoning_effort": thinkingSelection.thinkingLevel,
|
|
13152
|
+
...toGenAiMessagesTraceAttributes("app.ai.input", inputMessages),
|
|
13239
13153
|
...inputMessagesAttribute ? { "gen_ai.input.messages": inputMessagesAttribute } : {}
|
|
13240
13154
|
}
|
|
13241
13155
|
);
|
|
13242
13156
|
} finally {
|
|
13243
13157
|
unsubscribe();
|
|
13244
13158
|
}
|
|
13245
|
-
if (
|
|
13246
|
-
await
|
|
13159
|
+
if (turnSessionState.canUseTurnSession && sessionConversationId && sessionId) {
|
|
13160
|
+
await recordActiveMcpProviders();
|
|
13161
|
+
await persistCompletedSessionRecord({
|
|
13162
|
+
channelName: context.correlation?.channelName,
|
|
13247
13163
|
conversationId: sessionConversationId,
|
|
13248
13164
|
currentDurationMs: Date.now() - replyStartedAtMs,
|
|
13249
13165
|
currentUsage: turnUsage,
|
|
13250
13166
|
sessionId,
|
|
13251
13167
|
sliceId: currentSliceId,
|
|
13252
13168
|
allMessages: agent.state.messages,
|
|
13253
|
-
loadedSkillNames:
|
|
13254
|
-
logContext:
|
|
13169
|
+
loadedSkillNames: loadedSkillNamesForResume,
|
|
13170
|
+
logContext: sessionRecordLogContext,
|
|
13171
|
+
requester
|
|
13255
13172
|
});
|
|
13256
13173
|
}
|
|
13257
13174
|
return buildTurnResult({
|
|
@@ -13274,26 +13191,29 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13274
13191
|
} catch (error) {
|
|
13275
13192
|
if (timedOut && timeoutResumeConversationId && timeoutResumeSessionId) {
|
|
13276
13193
|
turnUsage = turnUsage ?? extractSliceUsage(timeoutResumeMessages, beforeMessageCount);
|
|
13277
|
-
|
|
13194
|
+
await recordActiveMcpProviders();
|
|
13195
|
+
const sessionRecord = await persistTimeoutSessionRecord({
|
|
13196
|
+
channelName: context.correlation?.channelName,
|
|
13278
13197
|
conversationId: timeoutResumeConversationId,
|
|
13279
13198
|
sessionId: timeoutResumeSessionId,
|
|
13280
13199
|
currentSliceId: timeoutResumeSliceId,
|
|
13281
13200
|
currentDurationMs: Date.now() - replyStartedAtMs,
|
|
13282
13201
|
currentUsage: turnUsage,
|
|
13283
13202
|
messages: timeoutResumeMessages,
|
|
13284
|
-
loadedSkillNames: loadedSkillNamesForResume,
|
|
13285
13203
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
13286
|
-
|
|
13204
|
+
loadedSkillNames: loadedSkillNamesForResume,
|
|
13205
|
+
logContext: sessionRecordLogContext,
|
|
13206
|
+
requester
|
|
13287
13207
|
});
|
|
13288
|
-
if (
|
|
13208
|
+
if (sessionRecord) {
|
|
13289
13209
|
throw new RetryableTurnError(
|
|
13290
13210
|
"turn_timeout_resume",
|
|
13291
|
-
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${
|
|
13211
|
+
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${sessionRecord.sliceId} version=${sessionRecord.version}`,
|
|
13292
13212
|
{
|
|
13293
13213
|
conversationId: timeoutResumeConversationId,
|
|
13294
13214
|
sessionId: timeoutResumeSessionId,
|
|
13295
|
-
sliceId:
|
|
13296
|
-
|
|
13215
|
+
sliceId: sessionRecord.sliceId,
|
|
13216
|
+
version: sessionRecord.version
|
|
13297
13217
|
}
|
|
13298
13218
|
);
|
|
13299
13219
|
}
|
|
@@ -13305,21 +13225,24 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13305
13225
|
beforeMessageCount
|
|
13306
13226
|
);
|
|
13307
13227
|
}
|
|
13308
|
-
|
|
13228
|
+
await recordActiveMcpProviders();
|
|
13229
|
+
const sessionRecord = await persistAuthPauseSessionRecord({
|
|
13230
|
+
channelName: context.correlation?.channelName,
|
|
13309
13231
|
conversationId: timeoutResumeConversationId,
|
|
13310
13232
|
sessionId: timeoutResumeSessionId,
|
|
13311
13233
|
currentSliceId: timeoutResumeSliceId,
|
|
13312
13234
|
currentDurationMs: Date.now() - replyStartedAtMs,
|
|
13313
13235
|
currentUsage: turnUsage,
|
|
13314
13236
|
messages: timeoutResumeMessages,
|
|
13315
|
-
loadedSkillNames: loadedSkillNamesForResume,
|
|
13316
13237
|
errorMessage: error.message,
|
|
13317
|
-
|
|
13238
|
+
loadedSkillNames: loadedSkillNamesForResume,
|
|
13239
|
+
logContext: sessionRecordLogContext,
|
|
13240
|
+
requester
|
|
13318
13241
|
});
|
|
13319
|
-
if (
|
|
13242
|
+
if (sessionRecord) {
|
|
13320
13243
|
throw new RetryableTurnError(
|
|
13321
13244
|
error.kind === "plugin" ? "plugin_auth_resume" : "mcp_auth_resume",
|
|
13322
|
-
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${
|
|
13245
|
+
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${sessionRecord.sliceId}`,
|
|
13323
13246
|
{
|
|
13324
13247
|
authDisposition: error.disposition,
|
|
13325
13248
|
authDurationMs: Date.now() - replyStartedAtMs,
|
|
@@ -13329,7 +13252,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13329
13252
|
authUsage: turnUsage,
|
|
13330
13253
|
conversationId: timeoutResumeConversationId,
|
|
13331
13254
|
sessionId: timeoutResumeSessionId,
|
|
13332
|
-
sliceId:
|
|
13255
|
+
sliceId: sessionRecord.sliceId
|
|
13333
13256
|
}
|
|
13334
13257
|
);
|
|
13335
13258
|
}
|
|
@@ -13586,8 +13509,8 @@ async function summarizeConversationChunk(messages, conversation, context, deps)
|
|
|
13586
13509
|
"Keep the summary factual and concise. Do not invent details.",
|
|
13587
13510
|
"",
|
|
13588
13511
|
"Output exactly three XML sections in this order:",
|
|
13589
|
-
"<active-asks> one bullet per outstanding user ask that has not been narrowed, answered, or
|
|
13590
|
-
"<
|
|
13512
|
+
"<active-asks> one bullet per outstanding user ask that has not been narrowed, answered, or replaced by a later turn. Omit the section body if none. </active-asks>",
|
|
13513
|
+
"<resolved-or-replaced-asks> one bullet per ask that has been replaced, narrowed, answered, or already acted on in this segment. Include the replacement/outcome inline. Omit the section body if none. </resolved-or-replaced-asks>",
|
|
13591
13514
|
"<facts> one bullet per durable fact useful regardless of scope: names, ids, URLs, decisions, locations, preferences, constraints that remain true. Omit the section body if none. </facts>",
|
|
13592
13515
|
"",
|
|
13593
13516
|
"Do not output any text outside the three sections.",
|
|
@@ -13740,40 +13663,6 @@ function escapeSlackMrkdwn(text) {
|
|
|
13740
13663
|
function escapeSlackLinkUrl(url) {
|
|
13741
13664
|
return url.replaceAll("&", "&").replaceAll("<", "%3C").replaceAll(">", "%3E");
|
|
13742
13665
|
}
|
|
13743
|
-
function getSentryOrgSlug() {
|
|
13744
|
-
const slug = process.env.SENTRY_ORG_SLUG?.trim();
|
|
13745
|
-
return slug || void 0;
|
|
13746
|
-
}
|
|
13747
|
-
function isSentrySaasDsnHost(host) {
|
|
13748
|
-
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
13749
|
-
}
|
|
13750
|
-
function buildSentryWebBaseUrl(dsn) {
|
|
13751
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
13752
|
-
return "https://sentry.io";
|
|
13753
|
-
}
|
|
13754
|
-
const port = dsn.port ? `:${dsn.port}` : "";
|
|
13755
|
-
const path11 = dsn.path ? `/${dsn.path}` : "";
|
|
13756
|
-
return `${dsn.protocol}://${dsn.host}${port}${path11}`;
|
|
13757
|
-
}
|
|
13758
|
-
function getSentryConversationUrl(conversationId) {
|
|
13759
|
-
const client2 = sentry_exports.getClient();
|
|
13760
|
-
const dsn = client2?.getDsn();
|
|
13761
|
-
if (!dsn?.host || !dsn.projectId) {
|
|
13762
|
-
return void 0;
|
|
13763
|
-
}
|
|
13764
|
-
const orgSlug = getSentryOrgSlug();
|
|
13765
|
-
if (!orgSlug) {
|
|
13766
|
-
return void 0;
|
|
13767
|
-
}
|
|
13768
|
-
const encodedId = encodeURIComponent(conversationId);
|
|
13769
|
-
const params = new URLSearchParams();
|
|
13770
|
-
params.set("project", dsn.projectId);
|
|
13771
|
-
const path11 = `explore/conversations/${encodedId}/?${params.toString()}`;
|
|
13772
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
13773
|
-
return `https://${orgSlug}.sentry.io/${path11}`;
|
|
13774
|
-
}
|
|
13775
|
-
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path11}`;
|
|
13776
|
-
}
|
|
13777
13666
|
function formatSlackTokenCount(value) {
|
|
13778
13667
|
if (value >= 1e6) {
|
|
13779
13668
|
const millions = value / 1e6;
|
|
@@ -13827,7 +13716,7 @@ function buildSlackReplyFooter(args) {
|
|
|
13827
13716
|
label: "ID",
|
|
13828
13717
|
value: conversationId
|
|
13829
13718
|
};
|
|
13830
|
-
const conversationUrl =
|
|
13719
|
+
const conversationUrl = buildSentryConversationUrl(conversationId);
|
|
13831
13720
|
if (conversationUrl) {
|
|
13832
13721
|
idItem.url = conversationUrl;
|
|
13833
13722
|
}
|
|
@@ -14130,17 +14019,17 @@ function canScheduleTurnTimeoutResume(nextSliceId) {
|
|
|
14130
14019
|
return typeof nextSliceId === "number" && nextSliceId > 1 && nextSliceId <= MAX_TURN_TIMEOUT_RESUME_SLICE_ID;
|
|
14131
14020
|
}
|
|
14132
14021
|
async function getAwaitingTurnContinuationRequest(args) {
|
|
14133
|
-
const
|
|
14022
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
14134
14023
|
args.conversationId,
|
|
14135
14024
|
args.sessionId
|
|
14136
14025
|
);
|
|
14137
|
-
if (!
|
|
14026
|
+
if (!sessionRecord || sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "timeout" || !canScheduleTurnTimeoutResume(sessionRecord.sliceId)) {
|
|
14138
14027
|
return void 0;
|
|
14139
14028
|
}
|
|
14140
14029
|
return {
|
|
14141
14030
|
conversationId: args.conversationId,
|
|
14142
14031
|
sessionId: args.sessionId,
|
|
14143
|
-
|
|
14032
|
+
expectedVersion: sessionRecord.version
|
|
14144
14033
|
};
|
|
14145
14034
|
}
|
|
14146
14035
|
function getTurnTimeoutResumeSecret() {
|
|
@@ -14166,13 +14055,14 @@ function parseTurnTimeoutResumeRequest(value) {
|
|
|
14166
14055
|
return void 0;
|
|
14167
14056
|
}
|
|
14168
14057
|
const record = value;
|
|
14169
|
-
|
|
14058
|
+
const expectedVersion = typeof record.expectedVersion === "number" ? record.expectedVersion : record.expectedCheckpointVersion;
|
|
14059
|
+
if (typeof record.conversationId !== "string" || typeof record.sessionId !== "string" || typeof expectedVersion !== "number") {
|
|
14170
14060
|
return void 0;
|
|
14171
14061
|
}
|
|
14172
14062
|
return {
|
|
14173
14063
|
conversationId: record.conversationId,
|
|
14174
14064
|
sessionId: record.sessionId,
|
|
14175
|
-
|
|
14065
|
+
expectedVersion
|
|
14176
14066
|
};
|
|
14177
14067
|
}
|
|
14178
14068
|
async function scheduleTurnTimeoutResume(request) {
|
|
@@ -14328,7 +14218,6 @@ async function verifyDispatchCallbackRequest(request) {
|
|
|
14328
14218
|
|
|
14329
14219
|
// src/chat/agent-dispatch/store.ts
|
|
14330
14220
|
import { createHash as createHash2 } from "crypto";
|
|
14331
|
-
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
14332
14221
|
var DISPATCH_PREFIX = "junior:agent_dispatch";
|
|
14333
14222
|
var DISPATCH_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
14334
14223
|
var DISPATCH_INDEX_LOCK_TTL_MS = 1e4;
|
|
@@ -14359,9 +14248,12 @@ function buildDispatchId(plugin, idempotencyKey) {
|
|
|
14359
14248
|
const digest = createHash2("sha256").update(plugin).update("\0").update(idempotencyKey).digest("hex").slice(0, 32);
|
|
14360
14249
|
return `dispatch_${digest}`;
|
|
14361
14250
|
}
|
|
14362
|
-
function
|
|
14251
|
+
function getDispatchDestinationLockId(destination) {
|
|
14363
14252
|
return `slack:${destination.teamId}:${destination.channelId}`;
|
|
14364
14253
|
}
|
|
14254
|
+
function getDispatchConversationId(dispatch) {
|
|
14255
|
+
return `agent-dispatch:${dispatch.id}`;
|
|
14256
|
+
}
|
|
14365
14257
|
function getDispatchTurnId(dispatchId) {
|
|
14366
14258
|
return `dispatch:${dispatchId}`;
|
|
14367
14259
|
}
|
|
@@ -14419,7 +14311,7 @@ async function syncIncompleteDispatchIndex(state, record) {
|
|
|
14419
14311
|
await state.set(
|
|
14420
14312
|
incompleteDispatchIndexKey(),
|
|
14421
14313
|
next.slice(-DISPATCH_INDEX_MAX_LENGTH),
|
|
14422
|
-
|
|
14314
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
14423
14315
|
);
|
|
14424
14316
|
});
|
|
14425
14317
|
}
|
|
@@ -14427,7 +14319,7 @@ async function putRecord(state, record) {
|
|
|
14427
14319
|
await state.set(
|
|
14428
14320
|
getDispatchStorageKey(record.id),
|
|
14429
14321
|
record,
|
|
14430
|
-
|
|
14322
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
14431
14323
|
);
|
|
14432
14324
|
await syncIncompleteDispatchIndex(state, record);
|
|
14433
14325
|
}
|
|
@@ -14541,7 +14433,6 @@ async function markDispatch(args) {
|
|
|
14541
14433
|
...current,
|
|
14542
14434
|
status: args.status,
|
|
14543
14435
|
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
14544
|
-
...typeof args.resumeCheckpointVersion === "number" ? { resumeCheckpointVersion: args.resumeCheckpointVersion } : {},
|
|
14545
14436
|
...args.resultMessageTs ? { resultMessageTs: args.resultMessageTs } : {}
|
|
14546
14437
|
});
|
|
14547
14438
|
});
|
|
@@ -14578,14 +14469,15 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
14578
14469
|
return;
|
|
14579
14470
|
}
|
|
14580
14471
|
let dispatch = claimedDispatch;
|
|
14581
|
-
const conversationId = getDispatchConversationId(dispatch
|
|
14472
|
+
const conversationId = getDispatchConversationId(dispatch);
|
|
14473
|
+
const destinationLockId = getDispatchDestinationLockId(dispatch.destination);
|
|
14582
14474
|
const stateAdapter = getStateAdapter();
|
|
14583
14475
|
await stateAdapter.connect();
|
|
14584
|
-
const
|
|
14585
|
-
|
|
14476
|
+
const destinationLock = await stateAdapter.acquireLock(
|
|
14477
|
+
destinationLockId,
|
|
14586
14478
|
DISPATCH_SLICE_LEASE_MS
|
|
14587
14479
|
);
|
|
14588
|
-
if (!
|
|
14480
|
+
if (!destinationLock) {
|
|
14589
14481
|
await markDispatch({
|
|
14590
14482
|
dispatch,
|
|
14591
14483
|
status: "pending",
|
|
@@ -14774,12 +14666,11 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
14774
14666
|
return;
|
|
14775
14667
|
}
|
|
14776
14668
|
if (isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
14777
|
-
const
|
|
14669
|
+
const version = error.metadata?.version;
|
|
14778
14670
|
const nextSliceId = error.metadata?.sliceId;
|
|
14779
|
-
if (typeof
|
|
14671
|
+
if (typeof version === "number" && canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
14780
14672
|
const awaiting = await markDispatch({
|
|
14781
14673
|
dispatch,
|
|
14782
|
-
resumeCheckpointVersion: checkpointVersion,
|
|
14783
14674
|
status: "awaiting_resume"
|
|
14784
14675
|
});
|
|
14785
14676
|
await scheduleCallback({
|
|
@@ -14811,7 +14702,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
14811
14702
|
errorMessage: error instanceof Error ? error.message : String(error)
|
|
14812
14703
|
});
|
|
14813
14704
|
} finally {
|
|
14814
|
-
await stateAdapter.releaseLock(
|
|
14705
|
+
await stateAdapter.releaseLock(destinationLock);
|
|
14815
14706
|
}
|
|
14816
14707
|
}
|
|
14817
14708
|
|
|
@@ -15125,7 +15016,7 @@ function verifyHeartbeatRequest(request) {
|
|
|
15125
15016
|
const expected = Buffer.from(secret);
|
|
15126
15017
|
return actual.length === expected.length && timingSafeEqual4(actual, expected);
|
|
15127
15018
|
}
|
|
15128
|
-
async function
|
|
15019
|
+
async function GET2(request, waitUntil) {
|
|
15129
15020
|
if (!verifyHeartbeatRequest(request)) {
|
|
15130
15021
|
return new Response("Unauthorized", { status: 401 });
|
|
15131
15022
|
}
|
|
@@ -15144,6 +15035,9 @@ async function GET4(request, waitUntil) {
|
|
|
15144
15035
|
return new Response("Accepted", { status: 202 });
|
|
15145
15036
|
}
|
|
15146
15037
|
|
|
15038
|
+
// src/handlers/mcp-oauth-callback.ts
|
|
15039
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
15040
|
+
|
|
15147
15041
|
// src/chat/runtime/delivered-turn-state.ts
|
|
15148
15042
|
function buildDeliveredTurnStatePatch(args) {
|
|
15149
15043
|
const conversation = structuredClone(args.conversation);
|
|
@@ -15490,7 +15384,7 @@ function createSlackAdapterStatusSender(args) {
|
|
|
15490
15384
|
};
|
|
15491
15385
|
}
|
|
15492
15386
|
function createSlackWebApiStatusSender(args) {
|
|
15493
|
-
const
|
|
15387
|
+
const getClient2 = args.getSlackClient ?? getSlackClient;
|
|
15494
15388
|
return async (text, loadingMessages) => {
|
|
15495
15389
|
const channelId = args.channelId;
|
|
15496
15390
|
const threadTs = args.threadTs;
|
|
@@ -15503,7 +15397,7 @@ function createSlackWebApiStatusSender(args) {
|
|
|
15503
15397
|
}
|
|
15504
15398
|
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
15505
15399
|
try {
|
|
15506
|
-
await
|
|
15400
|
+
await getClient2().assistant.threads.setStatus({
|
|
15507
15401
|
channel_id: normalizedChannelId,
|
|
15508
15402
|
thread_ts: threadTs,
|
|
15509
15403
|
status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
@@ -15649,52 +15543,6 @@ function getWorkspaceTeamId() {
|
|
|
15649
15543
|
return workspaceTeamIdStorage.getStore();
|
|
15650
15544
|
}
|
|
15651
15545
|
|
|
15652
|
-
// src/chat/slack/context.ts
|
|
15653
|
-
function toTrimmedSlackString(value) {
|
|
15654
|
-
const normalized = toOptionalString(value);
|
|
15655
|
-
return normalized?.trim() || void 0;
|
|
15656
|
-
}
|
|
15657
|
-
function parseSlackThreadId(threadId) {
|
|
15658
|
-
const normalizedThreadId = toTrimmedSlackString(threadId);
|
|
15659
|
-
if (!normalizedThreadId) {
|
|
15660
|
-
return void 0;
|
|
15661
|
-
}
|
|
15662
|
-
const parts = normalizedThreadId.split(":");
|
|
15663
|
-
if (parts.length !== 3 || parts[0] !== "slack") {
|
|
15664
|
-
return void 0;
|
|
15665
|
-
}
|
|
15666
|
-
const channelId = toTrimmedSlackString(parts[1]);
|
|
15667
|
-
const threadTs = toTrimmedSlackString(parts[2]);
|
|
15668
|
-
if (!channelId || !threadTs) {
|
|
15669
|
-
return void 0;
|
|
15670
|
-
}
|
|
15671
|
-
return { channelId, threadTs };
|
|
15672
|
-
}
|
|
15673
|
-
function resolveSlackChannelIdFromThreadId(threadId) {
|
|
15674
|
-
return parseSlackThreadId(threadId)?.channelId;
|
|
15675
|
-
}
|
|
15676
|
-
function resolveSlackChannelIdFromMessage(message) {
|
|
15677
|
-
const messageChannelId = toTrimmedSlackString(
|
|
15678
|
-
message.channelId
|
|
15679
|
-
);
|
|
15680
|
-
if (messageChannelId) {
|
|
15681
|
-
return messageChannelId;
|
|
15682
|
-
}
|
|
15683
|
-
const raw = message.raw;
|
|
15684
|
-
if (raw && typeof raw === "object") {
|
|
15685
|
-
const rawChannel = toTrimmedSlackString(
|
|
15686
|
-
raw.channel
|
|
15687
|
-
);
|
|
15688
|
-
if (rawChannel) {
|
|
15689
|
-
return rawChannel;
|
|
15690
|
-
}
|
|
15691
|
-
}
|
|
15692
|
-
const threadId = toTrimmedSlackString(
|
|
15693
|
-
message.threadId
|
|
15694
|
-
);
|
|
15695
|
-
return resolveSlackChannelIdFromThreadId(threadId);
|
|
15696
|
-
}
|
|
15697
|
-
|
|
15698
15546
|
// src/chat/runtime/thread-context.ts
|
|
15699
15547
|
function toSlackTeamId(value) {
|
|
15700
15548
|
const candidate = toOptionalString(value);
|
|
@@ -16070,7 +15918,7 @@ async function resumeSlackTurn(args) {
|
|
|
16070
15918
|
status.start();
|
|
16071
15919
|
const generateReply = runArgs.generateReply ?? generateAssistantReply;
|
|
16072
15920
|
const replyContext = createResumeReplyContext(runArgs, status);
|
|
16073
|
-
const
|
|
15921
|
+
const priorSessionRecord = replyContext.correlation?.conversationId && replyContext.correlation?.turnId ? await getAgentTurnSessionRecord(
|
|
16074
15922
|
replyContext.correlation.conversationId,
|
|
16075
15923
|
replyContext.correlation.turnId
|
|
16076
15924
|
) : void 0;
|
|
@@ -16097,10 +15945,10 @@ async function resumeSlackTurn(args) {
|
|
|
16097
15945
|
await status.stop();
|
|
16098
15946
|
const footer = buildSlackReplyFooter({
|
|
16099
15947
|
conversationId: runArgs.replyContext?.correlation?.conversationId ?? lockKey,
|
|
16100
|
-
durationMs: typeof
|
|
15948
|
+
durationMs: typeof priorSessionRecord?.cumulativeDurationMs === "number" || typeof reply.diagnostics.durationMs === "number" ? (priorSessionRecord?.cumulativeDurationMs ?? 0) + (reply.diagnostics.durationMs ?? 0) : void 0,
|
|
16101
15949
|
thinkingLevel: reply.diagnostics.thinkingLevel,
|
|
16102
15950
|
usage: addAgentTurnUsage(
|
|
16103
|
-
|
|
15951
|
+
priorSessionRecord?.cumulativeUsage,
|
|
16104
15952
|
reply.diagnostics.usage
|
|
16105
15953
|
) ?? reply.diagnostics.usage
|
|
16106
15954
|
});
|
|
@@ -16291,6 +16139,9 @@ var CALLBACK_PAGES = {
|
|
|
16291
16139
|
status: 500
|
|
16292
16140
|
}
|
|
16293
16141
|
};
|
|
16142
|
+
function mcpAuthorizationId(args) {
|
|
16143
|
+
return `${args.sessionId}:mcp:${args.provider}`;
|
|
16144
|
+
}
|
|
16294
16145
|
function htmlResponse(kind) {
|
|
16295
16146
|
const page = CALLBACK_PAGES[kind];
|
|
16296
16147
|
return htmlCallbackResponse(page.title, page.message, page.status);
|
|
@@ -16312,27 +16163,28 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
16312
16163
|
...statePatch
|
|
16313
16164
|
});
|
|
16314
16165
|
}
|
|
16315
|
-
async function
|
|
16166
|
+
async function failSessionRecordBestEffort(args) {
|
|
16316
16167
|
try {
|
|
16317
|
-
await
|
|
16168
|
+
await failAgentTurnSessionRecord({
|
|
16318
16169
|
conversationId: args.conversationId,
|
|
16319
16170
|
sessionId: args.sessionId,
|
|
16320
|
-
errorMessage: args.errorMessage
|
|
16171
|
+
errorMessage: args.errorMessage,
|
|
16172
|
+
expectedVersion: args.expectedVersion
|
|
16321
16173
|
});
|
|
16322
16174
|
} catch (error) {
|
|
16323
16175
|
logException(
|
|
16324
16176
|
error,
|
|
16325
|
-
"
|
|
16177
|
+
"mcp_oauth_callback_session_record_fail_persist_failed",
|
|
16326
16178
|
{},
|
|
16327
16179
|
{
|
|
16328
16180
|
"app.ai.conversation_id": args.conversationId,
|
|
16329
16181
|
"app.ai.session_id": args.sessionId
|
|
16330
16182
|
},
|
|
16331
|
-
"Failed to mark MCP OAuth-resumed turn
|
|
16183
|
+
"Failed to mark MCP OAuth-resumed turn session record failed"
|
|
16332
16184
|
);
|
|
16333
16185
|
}
|
|
16334
16186
|
}
|
|
16335
|
-
async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
16187
|
+
async function persistFailedReplyState(channelId, threadTs, sessionId, expectedVersion) {
|
|
16336
16188
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
16337
16189
|
const currentState = await getPersistedThreadState(threadId);
|
|
16338
16190
|
const conversation = coerceThreadConversationState(currentState);
|
|
@@ -16345,10 +16197,11 @@ async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
|
16345
16197
|
markConversationMessage,
|
|
16346
16198
|
updateConversationStats
|
|
16347
16199
|
});
|
|
16348
|
-
await
|
|
16200
|
+
await failSessionRecordBestEffort({
|
|
16349
16201
|
conversationId: threadId,
|
|
16350
16202
|
sessionId,
|
|
16351
|
-
errorMessage: "OAuth-resumed MCP turn failed"
|
|
16203
|
+
errorMessage: "OAuth-resumed MCP turn failed",
|
|
16204
|
+
expectedVersion
|
|
16352
16205
|
});
|
|
16353
16206
|
await persistThreadStateById(threadId, {
|
|
16354
16207
|
conversation
|
|
@@ -16374,10 +16227,10 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16374
16227
|
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
16375
16228
|
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
16376
16229
|
await persistThreadStateById(threadId, { conversation });
|
|
16377
|
-
await
|
|
16230
|
+
await abandonAgentTurnSessionRecord({
|
|
16378
16231
|
conversationId: authSession.conversationId,
|
|
16379
16232
|
sessionId: pendingAuth.sessionId,
|
|
16380
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16233
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16381
16234
|
});
|
|
16382
16235
|
return;
|
|
16383
16236
|
}
|
|
@@ -16414,10 +16267,10 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16414
16267
|
await persistThreadStateById(threadId, {
|
|
16415
16268
|
conversation: lockedConversation
|
|
16416
16269
|
});
|
|
16417
|
-
await
|
|
16270
|
+
await abandonAgentTurnSessionRecord({
|
|
16418
16271
|
conversationId: authSession.conversationId,
|
|
16419
16272
|
sessionId: lockedPendingAuth.sessionId,
|
|
16420
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16273
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16421
16274
|
});
|
|
16422
16275
|
return false;
|
|
16423
16276
|
}
|
|
@@ -16431,6 +16284,13 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16431
16284
|
if (!lockedUserMessage) {
|
|
16432
16285
|
return false;
|
|
16433
16286
|
}
|
|
16287
|
+
const lockedSessionRecord = await getAgentTurnSessionRecord(
|
|
16288
|
+
authSession.conversationId,
|
|
16289
|
+
lockedSessionId
|
|
16290
|
+
);
|
|
16291
|
+
if (!lockedSessionRecord || lockedSessionRecord.state !== "awaiting_resume" || lockedSessionRecord.resumeReason !== "auth") {
|
|
16292
|
+
return false;
|
|
16293
|
+
}
|
|
16434
16294
|
const lockedConversationContext = buildConversationContext(
|
|
16435
16295
|
lockedConversation,
|
|
16436
16296
|
{
|
|
@@ -16440,6 +16300,17 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16440
16300
|
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
16441
16301
|
authSession.channelId
|
|
16442
16302
|
);
|
|
16303
|
+
await recordAuthorizationCompleted({
|
|
16304
|
+
conversationId: authSession.conversationId,
|
|
16305
|
+
kind: "mcp",
|
|
16306
|
+
provider,
|
|
16307
|
+
requesterId: authSession.userId,
|
|
16308
|
+
authorizationId: mcpAuthorizationId({
|
|
16309
|
+
provider,
|
|
16310
|
+
sessionId: lockedSessionId
|
|
16311
|
+
}),
|
|
16312
|
+
ttlMs: THREAD_STATE_TTL_MS4
|
|
16313
|
+
});
|
|
16443
16314
|
return {
|
|
16444
16315
|
messageText: lockedUserMessage.text,
|
|
16445
16316
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
@@ -16485,8 +16356,9 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16485
16356
|
);
|
|
16486
16357
|
},
|
|
16487
16358
|
onPostDeliveryCommitFailure: async () => {
|
|
16488
|
-
await
|
|
16359
|
+
await failAgentTurnSessionRecord({
|
|
16489
16360
|
conversationId: authSession.conversationId,
|
|
16361
|
+
expectedVersion: lockedSessionRecord.version,
|
|
16490
16362
|
sessionId: lockedSessionId,
|
|
16491
16363
|
errorMessage: "OAuth-resumed MCP reply was delivered but completion state did not persist"
|
|
16492
16364
|
});
|
|
@@ -16496,7 +16368,8 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16496
16368
|
await persistFailedReplyState(
|
|
16497
16369
|
authSession.channelId,
|
|
16498
16370
|
authSession.threadTs,
|
|
16499
|
-
lockedSessionId
|
|
16371
|
+
lockedSessionId,
|
|
16372
|
+
lockedSessionRecord.version
|
|
16500
16373
|
);
|
|
16501
16374
|
} catch (persistError) {
|
|
16502
16375
|
logException(
|
|
@@ -16527,11 +16400,11 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16527
16400
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
16528
16401
|
throw error;
|
|
16529
16402
|
}
|
|
16530
|
-
const
|
|
16403
|
+
const version = error.metadata?.version;
|
|
16531
16404
|
const nextSliceId = error.metadata?.sliceId;
|
|
16532
|
-
if (typeof
|
|
16405
|
+
if (typeof version !== "number") {
|
|
16533
16406
|
throw new Error(
|
|
16534
|
-
"Timed-out MCP resume did not include a
|
|
16407
|
+
"Timed-out MCP resume did not include a turn-session version"
|
|
16535
16408
|
);
|
|
16536
16409
|
}
|
|
16537
16410
|
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
@@ -16551,14 +16424,14 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16551
16424
|
await scheduleTurnTimeoutResume({
|
|
16552
16425
|
conversationId: authSession.conversationId,
|
|
16553
16426
|
sessionId: lockedSessionId,
|
|
16554
|
-
|
|
16427
|
+
expectedVersion: version
|
|
16555
16428
|
});
|
|
16556
16429
|
}
|
|
16557
16430
|
};
|
|
16558
16431
|
}
|
|
16559
16432
|
});
|
|
16560
16433
|
}
|
|
16561
|
-
async function
|
|
16434
|
+
async function GET3(request, provider, waitUntil) {
|
|
16562
16435
|
const url = new URL(request.url);
|
|
16563
16436
|
const state = url.searchParams.get("state")?.trim();
|
|
16564
16437
|
const code = url.searchParams.get("code")?.trim();
|
|
@@ -16604,9 +16477,12 @@ async function GET5(request, provider, waitUntil) {
|
|
|
16604
16477
|
}
|
|
16605
16478
|
}
|
|
16606
16479
|
|
|
16480
|
+
// src/handlers/oauth-callback.ts
|
|
16481
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS5 } from "chat";
|
|
16482
|
+
|
|
16607
16483
|
// src/chat/slack/app-home.ts
|
|
16608
16484
|
import fs5 from "fs";
|
|
16609
|
-
import
|
|
16485
|
+
import path9 from "path";
|
|
16610
16486
|
var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
|
|
16611
16487
|
var MAX_HOME_SKILLS = 6;
|
|
16612
16488
|
var MAX_SECTION_TEXT_CHARS = 3e3;
|
|
@@ -16618,7 +16494,7 @@ function clampSectionText(text) {
|
|
|
16618
16494
|
return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
|
|
16619
16495
|
}
|
|
16620
16496
|
function loadDescriptionText() {
|
|
16621
|
-
const descriptionPath =
|
|
16497
|
+
const descriptionPath = path9.join(homeDir(), "DESCRIPTION.md");
|
|
16622
16498
|
try {
|
|
16623
16499
|
const raw = fs5.readFileSync(descriptionPath, "utf8").trim();
|
|
16624
16500
|
if (raw.length > 0) {
|
|
@@ -16769,23 +16645,27 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
16769
16645
|
...statePatch
|
|
16770
16646
|
});
|
|
16771
16647
|
}
|
|
16772
|
-
|
|
16648
|
+
function pluginAuthorizationId(args) {
|
|
16649
|
+
return `${args.sessionId}:plugin:${args.provider}`;
|
|
16650
|
+
}
|
|
16651
|
+
async function failSessionRecordBestEffort2(args) {
|
|
16773
16652
|
try {
|
|
16774
|
-
await
|
|
16653
|
+
await failAgentTurnSessionRecord({
|
|
16775
16654
|
conversationId: args.conversationId,
|
|
16655
|
+
expectedVersion: args.expectedVersion,
|
|
16776
16656
|
sessionId: args.sessionId,
|
|
16777
16657
|
errorMessage: args.errorMessage
|
|
16778
16658
|
});
|
|
16779
16659
|
} catch (error) {
|
|
16780
16660
|
logException(
|
|
16781
16661
|
error,
|
|
16782
|
-
"
|
|
16662
|
+
"oauth_callback_session_record_fail_persist_failed",
|
|
16783
16663
|
{},
|
|
16784
16664
|
{
|
|
16785
16665
|
"app.ai.conversation_id": args.conversationId,
|
|
16786
16666
|
"app.ai.session_id": args.sessionId
|
|
16787
16667
|
},
|
|
16788
|
-
"Failed to mark OAuth-resumed turn
|
|
16668
|
+
"Failed to mark OAuth-resumed turn session record failed"
|
|
16789
16669
|
);
|
|
16790
16670
|
}
|
|
16791
16671
|
}
|
|
@@ -16801,8 +16681,9 @@ async function persistFailedOAuthReplyState(args) {
|
|
|
16801
16681
|
markConversationMessage,
|
|
16802
16682
|
updateConversationStats
|
|
16803
16683
|
});
|
|
16804
|
-
await
|
|
16684
|
+
await failSessionRecordBestEffort2({
|
|
16805
16685
|
conversationId: args.conversationId,
|
|
16686
|
+
expectedVersion: args.expectedVersion,
|
|
16806
16687
|
sessionId: args.sessionId,
|
|
16807
16688
|
errorMessage: "OAuth-resumed turn failed"
|
|
16808
16689
|
});
|
|
@@ -16810,21 +16691,21 @@ async function persistFailedOAuthReplyState(args) {
|
|
|
16810
16691
|
conversation
|
|
16811
16692
|
});
|
|
16812
16693
|
}
|
|
16813
|
-
async function
|
|
16694
|
+
async function resumeOAuthSessionRecordTurn(stored) {
|
|
16814
16695
|
if (!stored.resumeConversationId || !stored.resumeSessionId || !stored.channelId || !stored.threadTs) {
|
|
16815
16696
|
return false;
|
|
16816
16697
|
}
|
|
16817
|
-
const
|
|
16698
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
16818
16699
|
stored.resumeConversationId,
|
|
16819
16700
|
stored.resumeSessionId
|
|
16820
16701
|
);
|
|
16821
|
-
if (!
|
|
16702
|
+
if (!sessionRecord) {
|
|
16822
16703
|
return false;
|
|
16823
16704
|
}
|
|
16824
|
-
if (
|
|
16705
|
+
if (sessionRecord.state === "completed" || sessionRecord.state === "failed" || sessionRecord.state === "abandoned") {
|
|
16825
16706
|
return true;
|
|
16826
16707
|
}
|
|
16827
|
-
if (
|
|
16708
|
+
if (sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "auth") {
|
|
16828
16709
|
return true;
|
|
16829
16710
|
}
|
|
16830
16711
|
const currentState = await getPersistedThreadState(
|
|
@@ -16845,10 +16726,10 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16845
16726
|
await persistThreadStateById(stored.resumeConversationId, {
|
|
16846
16727
|
conversation
|
|
16847
16728
|
});
|
|
16848
|
-
await
|
|
16729
|
+
await abandonAgentTurnSessionRecord({
|
|
16849
16730
|
conversationId: stored.resumeConversationId,
|
|
16850
16731
|
sessionId: pendingAuth.sessionId,
|
|
16851
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16732
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16852
16733
|
});
|
|
16853
16734
|
return true;
|
|
16854
16735
|
}
|
|
@@ -16871,11 +16752,11 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16871
16752
|
lockKey: stored.resumeConversationId,
|
|
16872
16753
|
initialText: "",
|
|
16873
16754
|
beforeStart: async () => {
|
|
16874
|
-
const
|
|
16755
|
+
const lockedSessionRecord = await getAgentTurnSessionRecord(
|
|
16875
16756
|
stored.resumeConversationId,
|
|
16876
16757
|
stored.resumeSessionId
|
|
16877
16758
|
);
|
|
16878
|
-
if (!
|
|
16759
|
+
if (!lockedSessionRecord || lockedSessionRecord.state !== "awaiting_resume" || lockedSessionRecord.resumeReason !== "auth") {
|
|
16879
16760
|
return false;
|
|
16880
16761
|
}
|
|
16881
16762
|
const lockedState = await getPersistedThreadState(
|
|
@@ -16899,10 +16780,10 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16899
16780
|
await persistThreadStateById(stored.resumeConversationId, {
|
|
16900
16781
|
conversation: lockedConversation
|
|
16901
16782
|
});
|
|
16902
|
-
await
|
|
16783
|
+
await abandonAgentTurnSessionRecord({
|
|
16903
16784
|
conversationId: stored.resumeConversationId,
|
|
16904
16785
|
sessionId: lockedPendingAuth.sessionId,
|
|
16905
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16786
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16906
16787
|
});
|
|
16907
16788
|
return false;
|
|
16908
16789
|
}
|
|
@@ -16925,6 +16806,17 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16925
16806
|
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
16926
16807
|
stored.channelId
|
|
16927
16808
|
);
|
|
16809
|
+
await recordAuthorizationCompleted({
|
|
16810
|
+
conversationId: stored.resumeConversationId,
|
|
16811
|
+
kind: "plugin",
|
|
16812
|
+
provider: stored.provider,
|
|
16813
|
+
requesterId: stored.userId,
|
|
16814
|
+
authorizationId: pluginAuthorizationId({
|
|
16815
|
+
provider: stored.provider,
|
|
16816
|
+
sessionId: lockedSessionId
|
|
16817
|
+
}),
|
|
16818
|
+
ttlMs: THREAD_STATE_TTL_MS5
|
|
16819
|
+
});
|
|
16928
16820
|
return {
|
|
16929
16821
|
messageText: stored.pendingMessage ?? lockedUserMessage.text,
|
|
16930
16822
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
@@ -16969,7 +16861,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16969
16861
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
16970
16862
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
16971
16863
|
},
|
|
16972
|
-
"OAuth callback auto-resumed
|
|
16864
|
+
"OAuth callback auto-resumed session record finished replying"
|
|
16973
16865
|
);
|
|
16974
16866
|
await persistCompletedOAuthReplyState({
|
|
16975
16867
|
conversationId: stored.resumeConversationId,
|
|
@@ -16978,9 +16870,9 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16978
16870
|
});
|
|
16979
16871
|
},
|
|
16980
16872
|
onPostDeliveryCommitFailure: async () => {
|
|
16981
|
-
await
|
|
16873
|
+
await failAgentTurnSessionRecord({
|
|
16982
16874
|
conversationId: stored.resumeConversationId,
|
|
16983
|
-
|
|
16875
|
+
expectedVersion: lockedSessionRecord.version,
|
|
16984
16876
|
sessionId: lockedSessionId,
|
|
16985
16877
|
errorMessage: "OAuth-resumed reply was delivered but completion state did not persist"
|
|
16986
16878
|
});
|
|
@@ -16988,6 +16880,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16988
16880
|
onFailure: async () => {
|
|
16989
16881
|
await persistFailedOAuthReplyState({
|
|
16990
16882
|
conversationId: stored.resumeConversationId,
|
|
16883
|
+
expectedVersion: lockedSessionRecord.version,
|
|
16991
16884
|
sessionId: lockedSessionId
|
|
16992
16885
|
});
|
|
16993
16886
|
},
|
|
@@ -17001,11 +16894,11 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
17001
16894
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
17002
16895
|
throw error;
|
|
17003
16896
|
}
|
|
17004
|
-
const
|
|
16897
|
+
const version = error.metadata?.version;
|
|
17005
16898
|
const nextSliceId = error.metadata?.sliceId;
|
|
17006
|
-
if (typeof
|
|
16899
|
+
if (typeof version !== "number") {
|
|
17007
16900
|
throw new Error(
|
|
17008
|
-
"Timed-out OAuth resume did not include a
|
|
16901
|
+
"Timed-out OAuth resume did not include a turn-session version"
|
|
17009
16902
|
);
|
|
17010
16903
|
}
|
|
17011
16904
|
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
@@ -17016,7 +16909,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
17016
16909
|
await scheduleTurnTimeoutResume({
|
|
17017
16910
|
conversationId: stored.resumeConversationId,
|
|
17018
16911
|
sessionId: lockedSessionId,
|
|
17019
|
-
|
|
16912
|
+
expectedVersion: version
|
|
17020
16913
|
});
|
|
17021
16914
|
}
|
|
17022
16915
|
};
|
|
@@ -17060,7 +16953,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
17060
16953
|
}
|
|
17061
16954
|
});
|
|
17062
16955
|
}
|
|
17063
|
-
async function
|
|
16956
|
+
async function GET4(request, provider, waitUntil) {
|
|
17064
16957
|
const providerConfig = getPluginOAuthConfig(provider);
|
|
17065
16958
|
if (!providerConfig) {
|
|
17066
16959
|
return htmlErrorResponse(
|
|
@@ -17199,7 +17092,7 @@ async function GET6(request, provider, waitUntil) {
|
|
|
17199
17092
|
if (stored.pendingMessage && stored.channelId && stored.threadTs) {
|
|
17200
17093
|
waitUntil(async () => {
|
|
17201
17094
|
try {
|
|
17202
|
-
const resumed = await
|
|
17095
|
+
const resumed = await resumeOAuthSessionRecordTurn(stored);
|
|
17203
17096
|
if (!resumed) {
|
|
17204
17097
|
await resumePendingOAuthMessage(stored);
|
|
17205
17098
|
}
|
|
@@ -17431,12 +17324,12 @@ function normalizePort(value) {
|
|
|
17431
17324
|
function sandboxIdFromPayload(payload) {
|
|
17432
17325
|
return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
|
|
17433
17326
|
}
|
|
17434
|
-
function normalizedForwardedPath(
|
|
17435
|
-
if (!
|
|
17327
|
+
function normalizedForwardedPath(path10) {
|
|
17328
|
+
if (!path10.startsWith("/") || path10.startsWith("//") || path10.includes("#") || /[\r\n]/.test(path10)) {
|
|
17436
17329
|
return { ok: false, error: "Invalid forwarded path" };
|
|
17437
17330
|
}
|
|
17438
17331
|
try {
|
|
17439
|
-
const url = new URL(
|
|
17332
|
+
const url = new URL(path10, "https://sandbox-forwarded.local");
|
|
17440
17333
|
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
17441
17334
|
} catch {
|
|
17442
17335
|
return { ok: false, error: "Invalid forwarded path" };
|
|
@@ -17471,13 +17364,13 @@ function buildUpstreamUrl(request) {
|
|
|
17471
17364
|
if (forwardedPort && !port) {
|
|
17472
17365
|
return { ok: false, error: "Invalid forwarded port" };
|
|
17473
17366
|
}
|
|
17474
|
-
const
|
|
17475
|
-
if (!
|
|
17476
|
-
return { ok: false, error:
|
|
17367
|
+
const path10 = upstreamPath(request);
|
|
17368
|
+
if (!path10.ok) {
|
|
17369
|
+
return { ok: false, error: path10.error };
|
|
17477
17370
|
}
|
|
17478
17371
|
try {
|
|
17479
17372
|
const url = new URL(
|
|
17480
|
-
`${scheme}://${host}${port ? `:${port}` : ""}${
|
|
17373
|
+
`${scheme}://${host}${port ? `:${port}` : ""}${path10.path}`
|
|
17481
17374
|
);
|
|
17482
17375
|
return { ok: true, url };
|
|
17483
17376
|
} catch {
|
|
@@ -17794,63 +17687,65 @@ function sleep4(ms) {
|
|
|
17794
17687
|
}
|
|
17795
17688
|
async function persistCompletedReplyState2(args) {
|
|
17796
17689
|
const currentState = await getPersistedThreadState(
|
|
17797
|
-
args.
|
|
17690
|
+
args.sessionRecord.conversationId
|
|
17798
17691
|
);
|
|
17799
17692
|
const conversation = coerceThreadConversationState(currentState);
|
|
17800
17693
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
17801
17694
|
const userMessage2 = getTurnUserMessage(
|
|
17802
17695
|
conversation,
|
|
17803
|
-
args.
|
|
17696
|
+
args.sessionRecord.sessionId
|
|
17804
17697
|
);
|
|
17805
17698
|
const statePatch = buildDeliveredTurnStatePatch({
|
|
17806
17699
|
artifacts,
|
|
17807
17700
|
conversation,
|
|
17808
17701
|
reply: args.reply,
|
|
17809
|
-
sessionId: args.
|
|
17702
|
+
sessionId: args.sessionRecord.sessionId,
|
|
17810
17703
|
userMessageId: userMessage2?.id
|
|
17811
17704
|
});
|
|
17812
|
-
await persistThreadStateById(args.
|
|
17705
|
+
await persistThreadStateById(args.sessionRecord.conversationId, {
|
|
17813
17706
|
...statePatch
|
|
17814
17707
|
});
|
|
17815
17708
|
}
|
|
17816
|
-
async function
|
|
17709
|
+
async function failSessionRecordBestEffort3(args) {
|
|
17817
17710
|
try {
|
|
17818
|
-
await
|
|
17819
|
-
conversationId: args.
|
|
17820
|
-
|
|
17821
|
-
sessionId: args.
|
|
17711
|
+
await failAgentTurnSessionRecord({
|
|
17712
|
+
conversationId: args.sessionRecord.conversationId,
|
|
17713
|
+
expectedVersion: args.sessionRecord.version,
|
|
17714
|
+
sessionId: args.sessionRecord.sessionId,
|
|
17822
17715
|
errorMessage: args.errorMessage
|
|
17823
17716
|
});
|
|
17824
17717
|
} catch (error) {
|
|
17825
17718
|
logException(
|
|
17826
17719
|
error,
|
|
17827
|
-
"
|
|
17720
|
+
"timeout_resume_session_record_fail_persist_failed",
|
|
17828
17721
|
{},
|
|
17829
17722
|
{
|
|
17830
|
-
"app.ai.conversation_id": args.
|
|
17831
|
-
"app.ai.session_id": args.
|
|
17723
|
+
"app.ai.conversation_id": args.sessionRecord.conversationId,
|
|
17724
|
+
"app.ai.session_id": args.sessionRecord.sessionId
|
|
17832
17725
|
},
|
|
17833
|
-
"Failed to mark timed-out turn
|
|
17726
|
+
"Failed to mark timed-out turn session record failed"
|
|
17834
17727
|
);
|
|
17835
17728
|
}
|
|
17836
17729
|
}
|
|
17837
|
-
async function persistFailedReplyState2(
|
|
17838
|
-
const currentState = await getPersistedThreadState(
|
|
17730
|
+
async function persistFailedReplyState2(sessionRecord) {
|
|
17731
|
+
const currentState = await getPersistedThreadState(
|
|
17732
|
+
sessionRecord.conversationId
|
|
17733
|
+
);
|
|
17839
17734
|
const conversation = coerceThreadConversationState(currentState);
|
|
17840
|
-
clearPendingAuth(conversation,
|
|
17735
|
+
clearPendingAuth(conversation, sessionRecord.sessionId);
|
|
17841
17736
|
markTurnFailed({
|
|
17842
17737
|
conversation,
|
|
17843
17738
|
nowMs: Date.now(),
|
|
17844
|
-
sessionId:
|
|
17845
|
-
userMessageId: getTurnUserMessage(conversation,
|
|
17739
|
+
sessionId: sessionRecord.sessionId,
|
|
17740
|
+
userMessageId: getTurnUserMessage(conversation, sessionRecord.sessionId)?.id,
|
|
17846
17741
|
markConversationMessage,
|
|
17847
17742
|
updateConversationStats
|
|
17848
17743
|
});
|
|
17849
|
-
await
|
|
17850
|
-
|
|
17744
|
+
await failSessionRecordBestEffort3({
|
|
17745
|
+
sessionRecord,
|
|
17851
17746
|
errorMessage: "Timed-out turn failed while resuming"
|
|
17852
17747
|
});
|
|
17853
|
-
await persistThreadStateById(
|
|
17748
|
+
await persistThreadStateById(sessionRecord.conversationId, {
|
|
17854
17749
|
conversation
|
|
17855
17750
|
});
|
|
17856
17751
|
}
|
|
@@ -17867,11 +17762,11 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17867
17762
|
threadTs: thread.threadTs,
|
|
17868
17763
|
lockKey: payload.conversationId,
|
|
17869
17764
|
beforeStart: async () => {
|
|
17870
|
-
const
|
|
17765
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
17871
17766
|
payload.conversationId,
|
|
17872
17767
|
payload.sessionId
|
|
17873
17768
|
);
|
|
17874
|
-
if (!
|
|
17769
|
+
if (!sessionRecord || sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "timeout" || sessionRecord.version !== payload.expectedVersion) {
|
|
17875
17770
|
return false;
|
|
17876
17771
|
}
|
|
17877
17772
|
const currentState = await getPersistedThreadState(
|
|
@@ -17931,16 +17826,16 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17931
17826
|
...getTurnUserReplyAttachmentContext(userMessage2)
|
|
17932
17827
|
},
|
|
17933
17828
|
onSuccess: async (reply) => {
|
|
17934
|
-
await persistCompletedReplyState2({
|
|
17829
|
+
await persistCompletedReplyState2({ sessionRecord, reply });
|
|
17935
17830
|
},
|
|
17936
17831
|
onFailure: async () => {
|
|
17937
|
-
await persistFailedReplyState2(
|
|
17832
|
+
await persistFailedReplyState2(sessionRecord);
|
|
17938
17833
|
},
|
|
17939
17834
|
onPostDeliveryCommitFailure: async () => {
|
|
17940
|
-
await
|
|
17941
|
-
conversationId:
|
|
17942
|
-
|
|
17943
|
-
sessionId:
|
|
17835
|
+
await failAgentTurnSessionRecord({
|
|
17836
|
+
conversationId: sessionRecord.conversationId,
|
|
17837
|
+
expectedVersion: sessionRecord.version,
|
|
17838
|
+
sessionId: sessionRecord.sessionId,
|
|
17944
17839
|
errorMessage: "Timed-out turn reply was delivered but completion state did not persist"
|
|
17945
17840
|
});
|
|
17946
17841
|
},
|
|
@@ -17963,11 +17858,11 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17963
17858
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
17964
17859
|
throw error;
|
|
17965
17860
|
}
|
|
17966
|
-
const
|
|
17861
|
+
const version = error.metadata?.version;
|
|
17967
17862
|
const nextSliceId = error.metadata?.sliceId;
|
|
17968
|
-
if (typeof
|
|
17863
|
+
if (typeof version !== "number") {
|
|
17969
17864
|
throw new Error(
|
|
17970
|
-
"Timed-out resume turn did not include a
|
|
17865
|
+
"Timed-out resume turn did not include a turn-session version"
|
|
17971
17866
|
);
|
|
17972
17867
|
}
|
|
17973
17868
|
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
@@ -17988,7 +17883,7 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17988
17883
|
await scheduleTurnTimeoutResume({
|
|
17989
17884
|
conversationId: payload.conversationId,
|
|
17990
17885
|
sessionId: payload.sessionId,
|
|
17991
|
-
|
|
17886
|
+
expectedVersion: version
|
|
17992
17887
|
});
|
|
17993
17888
|
}
|
|
17994
17889
|
};
|
|
@@ -18383,6 +18278,26 @@ async function decideSubscribedThreadReply(args) {
|
|
|
18383
18278
|
}
|
|
18384
18279
|
}
|
|
18385
18280
|
|
|
18281
|
+
// src/chat/runtime/turn-input.ts
|
|
18282
|
+
function combineTextParts(queuedTexts, latestText) {
|
|
18283
|
+
const parts = [...queuedTexts, latestText].filter(
|
|
18284
|
+
(part) => part.trim().length > 0
|
|
18285
|
+
);
|
|
18286
|
+
return parts.length > 0 ? parts.join("\n\n") : latestText;
|
|
18287
|
+
}
|
|
18288
|
+
function combineTurnText(queuedMessages, latestText) {
|
|
18289
|
+
return {
|
|
18290
|
+
rawText: combineTextParts(
|
|
18291
|
+
queuedMessages.map((message) => message.rawText),
|
|
18292
|
+
latestText.rawText
|
|
18293
|
+
),
|
|
18294
|
+
userText: combineTextParts(
|
|
18295
|
+
queuedMessages.map((message) => message.userText),
|
|
18296
|
+
latestText.userText
|
|
18297
|
+
)
|
|
18298
|
+
};
|
|
18299
|
+
}
|
|
18300
|
+
|
|
18386
18301
|
// src/chat/runtime/slack-runtime.ts
|
|
18387
18302
|
var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
|
|
18388
18303
|
async function maybeHandleThreadOptOutDecision(args) {
|
|
@@ -18394,6 +18309,19 @@ async function maybeHandleThreadOptOutDecision(args) {
|
|
|
18394
18309
|
await args.thread.post(THREAD_OPTOUT_ACK);
|
|
18395
18310
|
return true;
|
|
18396
18311
|
}
|
|
18312
|
+
function getQueuedMessages(context, options) {
|
|
18313
|
+
return (context?.skipped ?? []).map((message) => {
|
|
18314
|
+
const stripped = options.stripLeadingBotMention(message.text, {
|
|
18315
|
+
stripLeadingSlackMentionToken: options.explicitMention || Boolean(message.isMention)
|
|
18316
|
+
});
|
|
18317
|
+
return {
|
|
18318
|
+
explicitMention: options.explicitMention || Boolean(message.isMention),
|
|
18319
|
+
message,
|
|
18320
|
+
rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
|
|
18321
|
+
userText: appendSlackLegacyAttachmentText(stripped, message.raw)
|
|
18322
|
+
};
|
|
18323
|
+
});
|
|
18324
|
+
}
|
|
18397
18325
|
function buildLogContext(deps, args) {
|
|
18398
18326
|
return {
|
|
18399
18327
|
conversationId: args.threadId ?? args.runId,
|
|
@@ -18463,7 +18391,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18463
18391
|
message: args.message,
|
|
18464
18392
|
decision: args.decision,
|
|
18465
18393
|
completedAtMs,
|
|
18466
|
-
|
|
18394
|
+
text: args.text
|
|
18467
18395
|
});
|
|
18468
18396
|
}
|
|
18469
18397
|
};
|
|
@@ -18493,9 +18421,14 @@ function createSlackTurnRuntime(deps) {
|
|
|
18493
18421
|
);
|
|
18494
18422
|
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
18495
18423
|
await thread.subscribe();
|
|
18424
|
+
const queuedMessages = getQueuedMessages(hooks?.messageContext, {
|
|
18425
|
+
explicitMention: true,
|
|
18426
|
+
stripLeadingBotMention: deps.stripLeadingBotMention
|
|
18427
|
+
});
|
|
18496
18428
|
await deps.replyToThread(thread, message, {
|
|
18497
18429
|
explicitMention: true,
|
|
18498
18430
|
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
18431
|
+
queuedMessages,
|
|
18499
18432
|
onToolInvocation: toolInvocationHook
|
|
18500
18433
|
});
|
|
18501
18434
|
});
|
|
@@ -18558,28 +18491,33 @@ function createSlackTurnRuntime(deps) {
|
|
|
18558
18491
|
const legacyAttachmentText = renderSlackLegacyAttachmentText(
|
|
18559
18492
|
message.raw
|
|
18560
18493
|
);
|
|
18561
|
-
const rawUserText = appendSlackLegacyAttachmentText(
|
|
18562
|
-
message.text,
|
|
18563
|
-
message.raw
|
|
18564
|
-
);
|
|
18565
18494
|
const strippedUserText = deps.stripLeadingBotMention(message.text, {
|
|
18566
18495
|
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
18567
18496
|
});
|
|
18568
|
-
const
|
|
18569
|
-
|
|
18570
|
-
|
|
18571
|
-
|
|
18497
|
+
const currentText = {
|
|
18498
|
+
rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
|
|
18499
|
+
userText: appendSlackLegacyAttachmentText(
|
|
18500
|
+
strippedUserText,
|
|
18501
|
+
message.raw
|
|
18502
|
+
)
|
|
18503
|
+
};
|
|
18572
18504
|
const threadContext = {
|
|
18573
18505
|
threadId,
|
|
18574
18506
|
requesterId: message.author.userId,
|
|
18575
18507
|
channelId,
|
|
18576
18508
|
runId
|
|
18577
18509
|
};
|
|
18510
|
+
const queuedMessages = getQueuedMessages(hooks?.messageContext, {
|
|
18511
|
+
explicitMention: Boolean(message.isMention),
|
|
18512
|
+
stripLeadingBotMention: deps.stripLeadingBotMention
|
|
18513
|
+
});
|
|
18514
|
+
const combinedText = combineTurnText(queuedMessages, currentText);
|
|
18515
|
+
const turnIsExplicitMention = Boolean(message.isMention) || queuedMessages.some((queued) => queued.explicitMention);
|
|
18578
18516
|
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
18579
18517
|
botUserName: deps.assistantUserName,
|
|
18580
|
-
rawText:
|
|
18581
|
-
text: userText,
|
|
18582
|
-
isExplicitMention:
|
|
18518
|
+
rawText: combinedText.rawText,
|
|
18519
|
+
text: combinedText.userText,
|
|
18520
|
+
isExplicitMention: turnIsExplicitMention
|
|
18583
18521
|
});
|
|
18584
18522
|
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
18585
18523
|
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
@@ -18588,27 +18526,30 @@ function createSlackTurnRuntime(deps) {
|
|
|
18588
18526
|
message,
|
|
18589
18527
|
decision: { shouldReply: false, reason },
|
|
18590
18528
|
context: threadContext,
|
|
18591
|
-
|
|
18529
|
+
text: combinedText
|
|
18592
18530
|
});
|
|
18593
18531
|
return;
|
|
18594
18532
|
}
|
|
18595
18533
|
const preparedState = await deps.prepareTurnState({
|
|
18596
18534
|
thread,
|
|
18597
18535
|
message,
|
|
18598
|
-
|
|
18536
|
+
text: currentText,
|
|
18599
18537
|
explicitMention: Boolean(message.isMention),
|
|
18600
|
-
context: threadContext
|
|
18538
|
+
context: threadContext,
|
|
18539
|
+
queuedMessages
|
|
18601
18540
|
});
|
|
18602
18541
|
await deps.persistPreparedState({
|
|
18603
18542
|
thread,
|
|
18604
18543
|
preparedState
|
|
18605
18544
|
});
|
|
18606
18545
|
const decision = await deps.decideSubscribedReply({
|
|
18607
|
-
rawText:
|
|
18608
|
-
text: userText,
|
|
18546
|
+
rawText: combinedText.rawText,
|
|
18547
|
+
text: combinedText.userText,
|
|
18609
18548
|
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
18610
|
-
hasAttachments: message.attachments.length > 0 ||
|
|
18611
|
-
|
|
18549
|
+
hasAttachments: message.attachments.length > 0 || queuedMessages.some(
|
|
18550
|
+
(queued) => queued.message.attachments.length > 0
|
|
18551
|
+
) || legacyAttachmentText !== "",
|
|
18552
|
+
isExplicitMention: turnIsExplicitMention,
|
|
18612
18553
|
context: threadContext
|
|
18613
18554
|
});
|
|
18614
18555
|
if (await maybeHandleThreadOptOutDecision({
|
|
@@ -18622,7 +18563,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18622
18563
|
decision,
|
|
18623
18564
|
context: threadContext,
|
|
18624
18565
|
preparedState,
|
|
18625
|
-
|
|
18566
|
+
text: combinedText
|
|
18626
18567
|
});
|
|
18627
18568
|
return;
|
|
18628
18569
|
}
|
|
@@ -18633,7 +18574,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18633
18574
|
decision,
|
|
18634
18575
|
context: threadContext,
|
|
18635
18576
|
preparedState,
|
|
18636
|
-
|
|
18577
|
+
text: combinedText
|
|
18637
18578
|
});
|
|
18638
18579
|
return;
|
|
18639
18580
|
}
|
|
@@ -18651,6 +18592,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18651
18592
|
explicitMention: Boolean(message.isMention),
|
|
18652
18593
|
preparedState,
|
|
18653
18594
|
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
18595
|
+
queuedMessages,
|
|
18654
18596
|
onToolInvocation: toolInvocationHook
|
|
18655
18597
|
});
|
|
18656
18598
|
});
|
|
@@ -18748,6 +18690,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18748
18690
|
}
|
|
18749
18691
|
|
|
18750
18692
|
// src/chat/services/context-compaction.ts
|
|
18693
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS6 } from "chat";
|
|
18751
18694
|
import {
|
|
18752
18695
|
estimateContextTokens,
|
|
18753
18696
|
estimateTokens
|
|
@@ -18930,22 +18873,8 @@ function buildReplacementHistory(args) {
|
|
|
18930
18873
|
${args.summary}`)
|
|
18931
18874
|
];
|
|
18932
18875
|
}
|
|
18933
|
-
function
|
|
18934
|
-
|
|
18935
|
-
}
|
|
18936
|
-
async function loadCompactionSource(args) {
|
|
18937
|
-
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
18938
|
-
args.conversationId,
|
|
18939
|
-
args.previousSessionId
|
|
18940
|
-
);
|
|
18941
|
-
if (!checkpoint) {
|
|
18942
|
-
return { reason: "missing_context" };
|
|
18943
|
-
}
|
|
18944
|
-
if (checkpoint.state !== "completed") {
|
|
18945
|
-
return { reason: "not_completed" };
|
|
18946
|
-
}
|
|
18947
|
-
const messages = checkpoint.piMessages;
|
|
18948
|
-
if (messages.length) {
|
|
18876
|
+
function loadCompactionSource(messages) {
|
|
18877
|
+
if (messages.length > 0) {
|
|
18949
18878
|
return {
|
|
18950
18879
|
estimatedTokens: estimateHistoryTokens(messages),
|
|
18951
18880
|
messages
|
|
@@ -18954,10 +18883,7 @@ async function loadCompactionSource(args) {
|
|
|
18954
18883
|
return { reason: "missing_context" };
|
|
18955
18884
|
}
|
|
18956
18885
|
async function maybeCompactWithDeps(args, deps) {
|
|
18957
|
-
const source =
|
|
18958
|
-
conversationId: args.conversationId,
|
|
18959
|
-
previousSessionId: args.previousSessionId
|
|
18960
|
-
});
|
|
18886
|
+
const source = loadCompactionSource(args.piMessages);
|
|
18961
18887
|
if ("reason" in source) {
|
|
18962
18888
|
return { compacted: false, reason: source.reason };
|
|
18963
18889
|
}
|
|
@@ -19004,29 +18930,22 @@ async function writeCompactedThreadContext(args, sourceMessages, summary, contex
|
|
|
19004
18930
|
messages: trimTrailingAssistantMessages(sourceMessages),
|
|
19005
18931
|
summary
|
|
19006
18932
|
});
|
|
19007
|
-
|
|
19008
|
-
await upsertAgentTurnSessionCheckpoint({
|
|
18933
|
+
await commitMessages({
|
|
19009
18934
|
conversationId: args.conversationId,
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
state: "completed",
|
|
19013
|
-
piMessages: replacement
|
|
18935
|
+
messages: replacement,
|
|
18936
|
+
ttlMs: THREAD_STATE_TTL_MS6
|
|
19014
18937
|
});
|
|
19015
|
-
args.conversation.processing.lastSessionId = nextSessionId;
|
|
19016
18938
|
updateConversationStats(args.conversation);
|
|
19017
18939
|
setSpanAttributes({
|
|
19018
18940
|
"app.compaction.input_messages": sourceMessages.length,
|
|
19019
18941
|
"app.compaction.retained_messages": replacement.length - 1,
|
|
19020
18942
|
"app.compaction.summary_chars": summary.length,
|
|
19021
|
-
"app.compaction.previous_session_id": args.previousSessionId,
|
|
19022
|
-
"app.compaction.next_session_id": nextSessionId,
|
|
19023
18943
|
...context.triggerTokens !== void 0 ? { "app.compaction.trigger_tokens": context.triggerTokens } : {},
|
|
19024
18944
|
"app.context_tokens_estimated": context.estimatedTokens
|
|
19025
18945
|
});
|
|
19026
18946
|
return {
|
|
19027
18947
|
compacted: true,
|
|
19028
|
-
piMessages: replacement
|
|
19029
|
-
sessionId: nextSessionId
|
|
18948
|
+
piMessages: replacement
|
|
19030
18949
|
};
|
|
19031
18950
|
}
|
|
19032
18951
|
function createContextCompactor(deps) {
|
|
@@ -19736,7 +19655,7 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
19736
19655
|
assistantThreadContext.threadTs,
|
|
19737
19656
|
title
|
|
19738
19657
|
);
|
|
19739
|
-
return titleSourceMessage.id;
|
|
19658
|
+
return { sourceMessageId: titleSourceMessage.id, title };
|
|
19740
19659
|
} catch (error) {
|
|
19741
19660
|
const slackErrorCode = getSlackApiErrorCode(error);
|
|
19742
19661
|
const assistantTitleErrorAttributes = {
|
|
@@ -19760,7 +19679,7 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
19760
19679
|
assistantTitleErrorAttributes,
|
|
19761
19680
|
"Skipping thread title update due to Slack permission error"
|
|
19762
19681
|
);
|
|
19763
|
-
return titleSourceMessage.id;
|
|
19682
|
+
return { sourceMessageId: titleSourceMessage.id };
|
|
19764
19683
|
}
|
|
19765
19684
|
logWarn(
|
|
19766
19685
|
"thread_title_generation_failed",
|
|
@@ -19814,6 +19733,27 @@ function collectCanvasUrls(artifacts) {
|
|
|
19814
19733
|
].filter((url) => typeof url === "string" && url !== "")
|
|
19815
19734
|
);
|
|
19816
19735
|
}
|
|
19736
|
+
function turnRequester(args) {
|
|
19737
|
+
const requester = {
|
|
19738
|
+
...args.email ? { email: args.email } : {},
|
|
19739
|
+
...args.fullName ? { fullName: args.fullName } : {},
|
|
19740
|
+
...args.userId ? { slackUserId: args.userId } : {},
|
|
19741
|
+
...args.userName ? { slackUserName: args.userName } : {}
|
|
19742
|
+
};
|
|
19743
|
+
return Object.keys(requester).length > 0 ? requester : void 0;
|
|
19744
|
+
}
|
|
19745
|
+
async function resolveChannelName(thread) {
|
|
19746
|
+
const existingName = thread.channel.name?.trim();
|
|
19747
|
+
if (existingName) {
|
|
19748
|
+
return existingName;
|
|
19749
|
+
}
|
|
19750
|
+
try {
|
|
19751
|
+
const metadata = await thread.channel.fetchMetadata();
|
|
19752
|
+
return metadata.name?.trim() || void 0;
|
|
19753
|
+
} catch {
|
|
19754
|
+
return void 0;
|
|
19755
|
+
}
|
|
19756
|
+
}
|
|
19817
19757
|
function getCurrentTurnCanvasUrl(args) {
|
|
19818
19758
|
const previousUrls = collectCanvasUrls(args.before);
|
|
19819
19759
|
const latestUrls = collectCanvasUrls(args.after);
|
|
@@ -19827,35 +19767,37 @@ function getCurrentTurnCanvasUrl(args) {
|
|
|
19827
19767
|
function buildCanvasRecoveryReply(canvasUrl) {
|
|
19828
19768
|
return `I created the canvas, but the turn was interrupted before I could finish the thread reply: ${canvasUrl}`;
|
|
19829
19769
|
}
|
|
19770
|
+
function collectTurnAttachments(message, queuedMessages) {
|
|
19771
|
+
return [
|
|
19772
|
+
...(queuedMessages ?? []).flatMap((queued) => queued.message.attachments),
|
|
19773
|
+
...message.attachments
|
|
19774
|
+
];
|
|
19775
|
+
}
|
|
19830
19776
|
async function loadPiMessagesForTurn(args) {
|
|
19831
19777
|
const fallback = args.fallback.length > 0 ? [...args.fallback] : void 0;
|
|
19832
19778
|
if (!args.conversationId) {
|
|
19833
19779
|
return { piMessages: fallback };
|
|
19834
19780
|
}
|
|
19835
19781
|
if (args.activeTurnId) {
|
|
19836
|
-
const
|
|
19782
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
19837
19783
|
args.conversationId,
|
|
19838
19784
|
args.activeTurnId
|
|
19839
19785
|
);
|
|
19840
|
-
if (
|
|
19786
|
+
if (sessionRecord?.piMessages.length) {
|
|
19841
19787
|
return {
|
|
19842
19788
|
piMessages: stripRuntimeTurnContext(
|
|
19843
|
-
trimTrailingAssistantMessages(
|
|
19789
|
+
trimTrailingAssistantMessages(sessionRecord.piMessages)
|
|
19844
19790
|
)
|
|
19845
19791
|
};
|
|
19846
19792
|
}
|
|
19847
19793
|
}
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
}
|
|
19851
|
-
|
|
19852
|
-
args.conversationId,
|
|
19853
|
-
args.lastSessionId
|
|
19854
|
-
);
|
|
19855
|
-
if (checkpoint?.state === "completed" && checkpoint.piMessages.length > 0) {
|
|
19794
|
+
const projection = await loadProjection({
|
|
19795
|
+
conversationId: args.conversationId
|
|
19796
|
+
});
|
|
19797
|
+
if (projection.length > 0) {
|
|
19856
19798
|
return {
|
|
19857
|
-
|
|
19858
|
-
piMessages:
|
|
19799
|
+
canCompact: true,
|
|
19800
|
+
piMessages: projection
|
|
19859
19801
|
};
|
|
19860
19802
|
}
|
|
19861
19803
|
return { piMessages: fallback };
|
|
@@ -19867,6 +19809,7 @@ function createReplyToThread(deps) {
|
|
|
19867
19809
|
}
|
|
19868
19810
|
const threadId = getThreadId(thread, message);
|
|
19869
19811
|
const channelId = getChannelId(thread, message);
|
|
19812
|
+
const channelName = channelId ? await resolveChannelName(thread) : void 0;
|
|
19870
19813
|
const threadTs = getThreadTs(threadId);
|
|
19871
19814
|
const assistantThreadContext = getAssistantThreadContext(message);
|
|
19872
19815
|
const messageTs = getMessageTs(message);
|
|
@@ -19889,17 +19832,25 @@ function createReplyToThread(deps) {
|
|
|
19889
19832
|
const strippedUserText = stripLeadingBotMention(message.text, {
|
|
19890
19833
|
stripLeadingSlackMentionToken: options.explicitMention || Boolean(message.isMention)
|
|
19891
19834
|
});
|
|
19892
|
-
const
|
|
19893
|
-
|
|
19894
|
-
|
|
19895
|
-
|
|
19835
|
+
const currentText = {
|
|
19836
|
+
rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
|
|
19837
|
+
userText: appendSlackLegacyAttachmentText(
|
|
19838
|
+
strippedUserText,
|
|
19839
|
+
message.raw
|
|
19840
|
+
)
|
|
19841
|
+
};
|
|
19842
|
+
const effectiveUserText = combineTurnText(
|
|
19843
|
+
options.queuedMessages ?? [],
|
|
19844
|
+
currentText
|
|
19845
|
+
).userText;
|
|
19896
19846
|
const preparedState = options.preparedState ?? await deps.prepareTurnState({
|
|
19897
19847
|
thread,
|
|
19898
19848
|
message,
|
|
19899
|
-
|
|
19849
|
+
text: currentText,
|
|
19900
19850
|
explicitMention: Boolean(
|
|
19901
19851
|
options.explicitMention || message.isMention
|
|
19902
19852
|
),
|
|
19853
|
+
queuedMessages: options.queuedMessages,
|
|
19903
19854
|
context: {
|
|
19904
19855
|
threadId,
|
|
19905
19856
|
requesterId: message.author.userId,
|
|
@@ -19909,6 +19860,15 @@ function createReplyToThread(deps) {
|
|
|
19909
19860
|
});
|
|
19910
19861
|
const slackMessageTs = getSlackMessageTs(message);
|
|
19911
19862
|
const turnId = buildDeterministicTurnId(message.id);
|
|
19863
|
+
const fallbackIdentity = await deps.services.lookupSlackUser(
|
|
19864
|
+
message.author.userId
|
|
19865
|
+
);
|
|
19866
|
+
const requester = turnRequester({
|
|
19867
|
+
email: fallbackIdentity?.email,
|
|
19868
|
+
fullName: message.author.fullName ?? fallbackIdentity?.fullName,
|
|
19869
|
+
userId: message.author.userId,
|
|
19870
|
+
userName: message.author.userName ?? fallbackIdentity?.userName
|
|
19871
|
+
});
|
|
19912
19872
|
const turnTraceContext = {
|
|
19913
19873
|
conversationId,
|
|
19914
19874
|
slackThreadId: threadId,
|
|
@@ -19990,7 +19950,7 @@ function createReplyToThread(deps) {
|
|
|
19990
19950
|
"agent_turn_continuation_retry_schedule_failed",
|
|
19991
19951
|
turnTraceContext,
|
|
19992
19952
|
{
|
|
19993
|
-
"app.ai.
|
|
19953
|
+
"app.ai.resume_session_version": resumeRequest.expectedVersion,
|
|
19994
19954
|
"app.ai.resume_session_id": resumeRequest.sessionId,
|
|
19995
19955
|
...messageTs ? { "messaging.message.id": messageTs } : {}
|
|
19996
19956
|
},
|
|
@@ -20013,11 +19973,10 @@ function createReplyToThread(deps) {
|
|
|
20013
19973
|
return;
|
|
20014
19974
|
}
|
|
20015
19975
|
}
|
|
20016
|
-
const lastSessionIdForHistory = preparedState.conversation.processing.lastSessionId;
|
|
20017
19976
|
const configReply = await maybeApplyProviderDefaultConfigRequest({
|
|
20018
19977
|
channelConfiguration: preparedState.channelConfiguration,
|
|
20019
19978
|
requesterId: message.author.userId,
|
|
20020
|
-
text:
|
|
19979
|
+
text: effectiveUserText
|
|
20021
19980
|
});
|
|
20022
19981
|
if (configReply) {
|
|
20023
19982
|
await beforeFirstResponsePost();
|
|
@@ -20053,6 +20012,28 @@ function createReplyToThread(deps) {
|
|
|
20053
20012
|
nextTurnId: turnId,
|
|
20054
20013
|
updateConversationStats
|
|
20055
20014
|
});
|
|
20015
|
+
if (conversationId) {
|
|
20016
|
+
void recordAgentTurnSessionSummary({
|
|
20017
|
+
channelName,
|
|
20018
|
+
conversationId,
|
|
20019
|
+
sessionId: turnId,
|
|
20020
|
+
sliceId: 1,
|
|
20021
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20022
|
+
state: "running",
|
|
20023
|
+
requester,
|
|
20024
|
+
traceId: getActiveTraceId()
|
|
20025
|
+
}).catch((error) => {
|
|
20026
|
+
logException(
|
|
20027
|
+
error,
|
|
20028
|
+
"agent_turn_summary_record_failed",
|
|
20029
|
+
turnTraceContext,
|
|
20030
|
+
{
|
|
20031
|
+
"app.agent.turn.state": "running"
|
|
20032
|
+
},
|
|
20033
|
+
"Failed to record running turn summary"
|
|
20034
|
+
);
|
|
20035
|
+
});
|
|
20036
|
+
}
|
|
20056
20037
|
setTags({
|
|
20057
20038
|
conversationId
|
|
20058
20039
|
});
|
|
@@ -20070,15 +20051,16 @@ function createReplyToThread(deps) {
|
|
|
20070
20051
|
await persistThreadState(thread, {
|
|
20071
20052
|
conversation: preparedState.conversation
|
|
20072
20053
|
});
|
|
20073
|
-
const fallbackIdentity = await deps.services.lookupSlackUser(
|
|
20074
|
-
message.author.userId
|
|
20075
|
-
);
|
|
20076
20054
|
const resolvedUserName = message.author.userName ?? fallbackIdentity?.userName;
|
|
20077
20055
|
if (resolvedUserName) {
|
|
20078
20056
|
setTags({ slackUserName: resolvedUserName });
|
|
20079
20057
|
}
|
|
20058
|
+
const turnAttachments = collectTurnAttachments(
|
|
20059
|
+
message,
|
|
20060
|
+
options.queuedMessages
|
|
20061
|
+
);
|
|
20080
20062
|
const userAttachments = await deps.resolveUserAttachments(
|
|
20081
|
-
|
|
20063
|
+
turnAttachments,
|
|
20082
20064
|
{
|
|
20083
20065
|
threadId,
|
|
20084
20066
|
requesterId: message.author.userId,
|
|
@@ -20088,7 +20070,7 @@ function createReplyToThread(deps) {
|
|
|
20088
20070
|
messageTs: slackMessageTs
|
|
20089
20071
|
}
|
|
20090
20072
|
);
|
|
20091
|
-
const omittedImageAttachmentCount = !isVisionEnabled() && hasPotentialImageAttachment(
|
|
20073
|
+
const omittedImageAttachmentCount = !isVisionEnabled() && hasPotentialImageAttachment(turnAttachments) ? countPotentialImageAttachments(turnAttachments) : 0;
|
|
20092
20074
|
const status = createSlackAdapterAssistantStatusSession({
|
|
20093
20075
|
channelId: assistantThreadContext?.channelId,
|
|
20094
20076
|
threadTs: assistantThreadContext?.threadTs,
|
|
@@ -20123,11 +20105,10 @@ function createReplyToThread(deps) {
|
|
|
20123
20105
|
const loadedPiMessages = await loadPiMessagesForTurn({
|
|
20124
20106
|
conversationId,
|
|
20125
20107
|
activeTurnId,
|
|
20126
|
-
lastSessionId: lastSessionIdForHistory,
|
|
20127
20108
|
fallback: preparedState.conversation.piMessages
|
|
20128
20109
|
});
|
|
20129
20110
|
let piMessages = loadedPiMessages.piMessages;
|
|
20130
|
-
if (conversationId && loadedPiMessages.
|
|
20111
|
+
if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
|
|
20131
20112
|
const compaction = await deps.services.contextCompactor.maybeCompact({
|
|
20132
20113
|
conversation: preparedState.conversation,
|
|
20133
20114
|
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
@@ -20139,7 +20120,7 @@ function createReplyToThread(deps) {
|
|
|
20139
20120
|
runId
|
|
20140
20121
|
},
|
|
20141
20122
|
onCompactionStart: () => status.start(compactingStatus),
|
|
20142
|
-
|
|
20123
|
+
piMessages
|
|
20143
20124
|
});
|
|
20144
20125
|
if (compaction.compacted) {
|
|
20145
20126
|
piMessages = compaction.piMessages;
|
|
@@ -20163,61 +20144,65 @@ function createReplyToThread(deps) {
|
|
|
20163
20144
|
threadId
|
|
20164
20145
|
});
|
|
20165
20146
|
const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
|
|
20166
|
-
let reply = await deps.services.generateAssistantReply(
|
|
20167
|
-
|
|
20168
|
-
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
|
|
20177
|
-
|
|
20178
|
-
|
|
20179
|
-
|
|
20180
|
-
|
|
20181
|
-
|
|
20182
|
-
|
|
20183
|
-
|
|
20184
|
-
|
|
20185
|
-
turnId,
|
|
20186
|
-
threadTs,
|
|
20187
|
-
messageTs,
|
|
20188
|
-
teamId,
|
|
20189
|
-
runId,
|
|
20190
|
-
channelId,
|
|
20191
|
-
requesterId: message.author.userId
|
|
20192
|
-
},
|
|
20193
|
-
toolChannelId,
|
|
20194
|
-
sandbox: {
|
|
20195
|
-
sandboxId: preparedState.sandboxId,
|
|
20196
|
-
sandboxDependencyProfileHash: preparedState.sandboxDependencyProfileHash
|
|
20197
|
-
},
|
|
20198
|
-
onSandboxAcquired: async (sandbox) => {
|
|
20199
|
-
await persistThreadState(thread, {
|
|
20200
|
-
sandboxId: sandbox.sandboxId,
|
|
20201
|
-
sandboxDependencyProfileHash: sandbox.sandboxDependencyProfileHash
|
|
20202
|
-
});
|
|
20203
|
-
},
|
|
20204
|
-
onArtifactStateUpdated: async (artifacts) => {
|
|
20205
|
-
latestArtifacts = artifacts;
|
|
20206
|
-
await persistThreadState(thread, { artifacts });
|
|
20207
|
-
},
|
|
20208
|
-
onAuthPending: async (pendingAuth) => {
|
|
20209
|
-
await applyPendingAuthUpdate({
|
|
20210
|
-
conversation: preparedState.conversation,
|
|
20147
|
+
let reply = await deps.services.generateAssistantReply(
|
|
20148
|
+
effectiveUserText,
|
|
20149
|
+
{
|
|
20150
|
+
requester: {
|
|
20151
|
+
userId: message.author.userId,
|
|
20152
|
+
userName: message.author.userName ?? fallbackIdentity?.userName,
|
|
20153
|
+
fullName: message.author.fullName ?? fallbackIdentity?.fullName,
|
|
20154
|
+
email: fallbackIdentity?.email
|
|
20155
|
+
},
|
|
20156
|
+
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
20157
|
+
artifactState: preparedState.artifacts,
|
|
20158
|
+
piMessages,
|
|
20159
|
+
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
20160
|
+
configuration: preparedState.configuration,
|
|
20161
|
+
channelConfiguration: preparedState.channelConfiguration,
|
|
20162
|
+
inboundAttachmentCount: turnAttachments.length,
|
|
20163
|
+
omittedImageAttachmentCount,
|
|
20164
|
+
userAttachments,
|
|
20165
|
+
correlation: {
|
|
20211
20166
|
conversationId,
|
|
20212
|
-
|
|
20213
|
-
|
|
20214
|
-
|
|
20215
|
-
|
|
20216
|
-
|
|
20217
|
-
|
|
20218
|
-
|
|
20219
|
-
|
|
20220
|
-
|
|
20167
|
+
threadId,
|
|
20168
|
+
turnId,
|
|
20169
|
+
threadTs,
|
|
20170
|
+
messageTs,
|
|
20171
|
+
teamId,
|
|
20172
|
+
runId,
|
|
20173
|
+
channelId,
|
|
20174
|
+
channelName,
|
|
20175
|
+
requesterId: message.author.userId
|
|
20176
|
+
},
|
|
20177
|
+
toolChannelId,
|
|
20178
|
+
sandbox: {
|
|
20179
|
+
sandboxId: preparedState.sandboxId,
|
|
20180
|
+
sandboxDependencyProfileHash: preparedState.sandboxDependencyProfileHash
|
|
20181
|
+
},
|
|
20182
|
+
onSandboxAcquired: async (sandbox) => {
|
|
20183
|
+
await persistThreadState(thread, {
|
|
20184
|
+
sandboxId: sandbox.sandboxId,
|
|
20185
|
+
sandboxDependencyProfileHash: sandbox.sandboxDependencyProfileHash
|
|
20186
|
+
});
|
|
20187
|
+
},
|
|
20188
|
+
onArtifactStateUpdated: async (artifacts) => {
|
|
20189
|
+
latestArtifacts = artifacts;
|
|
20190
|
+
await persistThreadState(thread, { artifacts });
|
|
20191
|
+
},
|
|
20192
|
+
onAuthPending: async (pendingAuth) => {
|
|
20193
|
+
await applyPendingAuthUpdate({
|
|
20194
|
+
conversation: preparedState.conversation,
|
|
20195
|
+
conversationId,
|
|
20196
|
+
nextPendingAuth: pendingAuth
|
|
20197
|
+
});
|
|
20198
|
+
await persistThreadState(thread, {
|
|
20199
|
+
conversation: preparedState.conversation
|
|
20200
|
+
});
|
|
20201
|
+
},
|
|
20202
|
+
onStatus: (nextStatus) => status.update(nextStatus),
|
|
20203
|
+
onToolInvocation: options.onToolInvocation
|
|
20204
|
+
}
|
|
20205
|
+
);
|
|
20221
20206
|
const diagnosticsContext = {
|
|
20222
20207
|
slackThreadId: threadId,
|
|
20223
20208
|
slackUserId: message.author.userId,
|
|
@@ -20305,7 +20290,10 @@ function createReplyToThread(deps) {
|
|
|
20305
20290
|
}
|
|
20306
20291
|
const titleUpdateResult = await assistantTitleTask;
|
|
20307
20292
|
if (titleUpdateResult) {
|
|
20308
|
-
artifactStatePatch.assistantTitleSourceMessageId = titleUpdateResult;
|
|
20293
|
+
artifactStatePatch.assistantTitleSourceMessageId = titleUpdateResult.sourceMessageId;
|
|
20294
|
+
if (titleUpdateResult.title) {
|
|
20295
|
+
artifactStatePatch.assistantTitle = titleUpdateResult.title;
|
|
20296
|
+
}
|
|
20309
20297
|
}
|
|
20310
20298
|
const completedState = buildDeliveredTurnStatePatch({
|
|
20311
20299
|
artifactStatePatch,
|
|
@@ -20318,6 +20306,21 @@ function createReplyToThread(deps) {
|
|
|
20318
20306
|
await persistThreadState(thread, {
|
|
20319
20307
|
...completedState
|
|
20320
20308
|
});
|
|
20309
|
+
if (conversationId) {
|
|
20310
|
+
await recordAgentTurnSessionSummary({
|
|
20311
|
+
channelName,
|
|
20312
|
+
conversationId,
|
|
20313
|
+
cumulativeDurationMs: reply.diagnostics.durationMs,
|
|
20314
|
+
cumulativeUsage: reply.diagnostics.usage,
|
|
20315
|
+
sessionId: turnId,
|
|
20316
|
+
sliceId: 1,
|
|
20317
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20318
|
+
state: "completed",
|
|
20319
|
+
conversationTitle: titleUpdateResult?.title,
|
|
20320
|
+
requester,
|
|
20321
|
+
traceId: getActiveTraceId()
|
|
20322
|
+
});
|
|
20323
|
+
}
|
|
20321
20324
|
preparedState.conversation = completedState.conversation;
|
|
20322
20325
|
persistedAtLeastOnce = true;
|
|
20323
20326
|
if (shouldEmitDevAgentTrace()) {
|
|
@@ -20349,14 +20352,14 @@ function createReplyToThread(deps) {
|
|
|
20349
20352
|
if (isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
20350
20353
|
const conversationIdForResume = error.metadata?.conversationId;
|
|
20351
20354
|
const sessionIdForResume = error.metadata?.sessionId;
|
|
20352
|
-
const
|
|
20355
|
+
const version = error.metadata?.version;
|
|
20353
20356
|
const nextSliceId = error.metadata?.sliceId;
|
|
20354
|
-
if (conversationIdForResume && sessionIdForResume && typeof
|
|
20357
|
+
if (conversationIdForResume && sessionIdForResume && typeof version === "number" && canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
20355
20358
|
try {
|
|
20356
20359
|
await deps.services.scheduleTurnTimeoutResume({
|
|
20357
20360
|
conversationId: conversationIdForResume,
|
|
20358
20361
|
sessionId: sessionIdForResume,
|
|
20359
|
-
|
|
20362
|
+
expectedVersion: version
|
|
20360
20363
|
});
|
|
20361
20364
|
shouldPersistFailureState = false;
|
|
20362
20365
|
} catch (scheduleError) {
|
|
@@ -20366,7 +20369,7 @@ function createReplyToThread(deps) {
|
|
|
20366
20369
|
turnTraceContext,
|
|
20367
20370
|
{
|
|
20368
20371
|
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
20369
|
-
"app.ai.
|
|
20372
|
+
"app.ai.resume_session_version": version
|
|
20370
20373
|
},
|
|
20371
20374
|
"Failed to schedule timeout resume callback"
|
|
20372
20375
|
);
|
|
@@ -20375,7 +20378,7 @@ function createReplyToThread(deps) {
|
|
|
20375
20378
|
}
|
|
20376
20379
|
await postTurnContinuationNotice();
|
|
20377
20380
|
return;
|
|
20378
|
-
} else if (conversationIdForResume && sessionIdForResume && typeof
|
|
20381
|
+
} else if (conversationIdForResume && sessionIdForResume && typeof version === "number") {
|
|
20379
20382
|
logWarn(
|
|
20380
20383
|
"agent_turn_timeout_resume_slice_limit_reached",
|
|
20381
20384
|
turnTraceContext,
|
|
@@ -20465,18 +20468,35 @@ function createReplyToThread(deps) {
|
|
|
20465
20468
|
});
|
|
20466
20469
|
if (conversationId) {
|
|
20467
20470
|
try {
|
|
20468
|
-
await
|
|
20471
|
+
await recordAgentTurnSessionSummary({
|
|
20472
|
+
channelName,
|
|
20469
20473
|
conversationId,
|
|
20470
20474
|
sessionId: turnId,
|
|
20471
|
-
|
|
20475
|
+
sliceId: 1,
|
|
20476
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20477
|
+
state: "failed",
|
|
20478
|
+
requester,
|
|
20479
|
+
traceId: getActiveTraceId()
|
|
20472
20480
|
});
|
|
20473
|
-
|
|
20481
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
20482
|
+
conversationId,
|
|
20483
|
+
turnId
|
|
20484
|
+
);
|
|
20485
|
+
if (sessionRecord) {
|
|
20486
|
+
await failAgentTurnSessionRecord({
|
|
20487
|
+
conversationId,
|
|
20488
|
+
expectedVersion: sessionRecord.version,
|
|
20489
|
+
sessionId: turnId,
|
|
20490
|
+
errorMessage: "Agent turn failed before final reply delivery completed"
|
|
20491
|
+
});
|
|
20492
|
+
}
|
|
20493
|
+
} catch (recordError) {
|
|
20474
20494
|
logException(
|
|
20475
|
-
|
|
20476
|
-
"
|
|
20495
|
+
recordError,
|
|
20496
|
+
"agent_turn_failed_session_record_persist_failed",
|
|
20477
20497
|
turnTraceContext,
|
|
20478
20498
|
{},
|
|
20479
|
-
"Failed to mark failed turn
|
|
20499
|
+
"Failed to mark failed turn session record"
|
|
20480
20500
|
);
|
|
20481
20501
|
}
|
|
20482
20502
|
}
|
|
@@ -20533,35 +20553,51 @@ async function refreshAssistantThreadContext(event) {
|
|
|
20533
20553
|
await syncAssistantThreadContext(event, { setInitialTitle: false });
|
|
20534
20554
|
}
|
|
20535
20555
|
|
|
20536
|
-
// src/chat/runtime/
|
|
20537
|
-
var
|
|
20538
|
-
function
|
|
20539
|
-
|
|
20540
|
-
|
|
20556
|
+
// src/chat/runtime/conversation-message.ts
|
|
20557
|
+
var NON_TEXT_MESSAGE_TEXT = "[non-text message]";
|
|
20558
|
+
function resolveMessageText(args) {
|
|
20559
|
+
const text = normalizeConversationText(args.text);
|
|
20560
|
+
return text || NON_TEXT_MESSAGE_TEXT;
|
|
20561
|
+
}
|
|
20562
|
+
function toConversationMessage(args) {
|
|
20563
|
+
const messageHasPotentialImageAttachment = hasPotentialImageAttachment(
|
|
20564
|
+
args.entry.attachments
|
|
20541
20565
|
);
|
|
20542
|
-
|
|
20543
|
-
function createConversationMessageFromSdkMessage(entry) {
|
|
20544
|
-
const enrichedText = appendSlackLegacyAttachmentText(entry.text, entry.raw);
|
|
20545
|
-
const rawText = normalizeConversationText(enrichedText);
|
|
20546
|
-
if (!rawText) {
|
|
20547
|
-
return null;
|
|
20548
|
-
}
|
|
20566
|
+
const imageAttachmentCount = messageHasPotentialImageAttachment ? countPotentialImageAttachments(args.entry.attachments) : 0;
|
|
20549
20567
|
return {
|
|
20550
|
-
id: entry.id,
|
|
20551
|
-
role: entry.author.isMe ? "assistant" : "user",
|
|
20552
|
-
text:
|
|
20553
|
-
createdAtMs: entry.metadata.dateSent.getTime(),
|
|
20568
|
+
id: args.entry.id,
|
|
20569
|
+
role: args.entry.author.isMe ? "assistant" : "user",
|
|
20570
|
+
text: resolveMessageText(args),
|
|
20571
|
+
createdAtMs: args.entry.metadata.dateSent.getTime(),
|
|
20554
20572
|
author: {
|
|
20555
|
-
userId: entry.author.userId,
|
|
20556
|
-
userName: entry.author.userName,
|
|
20557
|
-
fullName: entry.author.fullName,
|
|
20558
|
-
isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
|
|
20573
|
+
userId: args.entry.author.userId,
|
|
20574
|
+
userName: args.entry.author.userName,
|
|
20575
|
+
fullName: args.entry.author.fullName,
|
|
20576
|
+
isBot: typeof args.entry.author.isBot === "boolean" ? args.entry.author.isBot : void 0
|
|
20559
20577
|
},
|
|
20560
20578
|
meta: {
|
|
20561
|
-
|
|
20579
|
+
attachmentCount: args.entry.attachments.length,
|
|
20580
|
+
explicitMention: args.explicitMention,
|
|
20581
|
+
imageAttachmentCount: imageAttachmentCount > 0 ? imageAttachmentCount : void 0,
|
|
20582
|
+
imagesHydrated: !messageHasPotentialImageAttachment,
|
|
20583
|
+
slackTs: getSlackMessageTs(args.entry)
|
|
20562
20584
|
}
|
|
20563
20585
|
};
|
|
20564
20586
|
}
|
|
20587
|
+
|
|
20588
|
+
// src/chat/runtime/turn-preparation.ts
|
|
20589
|
+
var BACKFILL_MESSAGE_LIMIT = 80;
|
|
20590
|
+
function hasPendingImageHydration(conversation) {
|
|
20591
|
+
return conversation.messages.some(
|
|
20592
|
+
(message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
|
|
20593
|
+
);
|
|
20594
|
+
}
|
|
20595
|
+
function getBackfillText(entry) {
|
|
20596
|
+
const text = normalizeConversationText(
|
|
20597
|
+
appendSlackLegacyAttachmentText(entry.text, entry.raw)
|
|
20598
|
+
);
|
|
20599
|
+
return text || void 0;
|
|
20600
|
+
}
|
|
20565
20601
|
async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
20566
20602
|
if (conversation.backfill.completedAtMs) {
|
|
20567
20603
|
return;
|
|
@@ -20586,9 +20622,9 @@ async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
|
20586
20622
|
}
|
|
20587
20623
|
fetchedNewestFirst.reverse();
|
|
20588
20624
|
for (const entry of fetchedNewestFirst) {
|
|
20589
|
-
const
|
|
20590
|
-
if (
|
|
20591
|
-
seeded.push(
|
|
20625
|
+
const text = getBackfillText(entry);
|
|
20626
|
+
if (text) {
|
|
20627
|
+
seeded.push(toConversationMessage({ entry, text }));
|
|
20592
20628
|
}
|
|
20593
20629
|
}
|
|
20594
20630
|
if (seeded.length > 0) {
|
|
@@ -20603,9 +20639,9 @@ async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
|
20603
20639
|
}
|
|
20604
20640
|
const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
|
|
20605
20641
|
for (const entry of fromRecent) {
|
|
20606
|
-
const
|
|
20607
|
-
if (
|
|
20608
|
-
seeded.push(
|
|
20642
|
+
const text = getBackfillText(entry);
|
|
20643
|
+
if (text) {
|
|
20644
|
+
seeded.push(toConversationMessage({ entry, text }));
|
|
20609
20645
|
}
|
|
20610
20646
|
}
|
|
20611
20647
|
source = "recent_messages";
|
|
@@ -20642,35 +20678,26 @@ function createPrepareTurnState(deps) {
|
|
|
20642
20678
|
messageId: args.message.id,
|
|
20643
20679
|
messageCreatedAtMs: args.message.metadata.dateSent.getTime()
|
|
20644
20680
|
});
|
|
20645
|
-
const
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
20658
|
-
userName: args.message.author.userName,
|
|
20659
|
-
fullName: args.message.author.fullName,
|
|
20660
|
-
isBot: typeof args.message.author.isBot === "boolean" ? args.message.author.isBot : void 0
|
|
20661
|
-
},
|
|
20662
|
-
meta: {
|
|
20663
|
-
attachmentCount: args.message.attachments.length,
|
|
20664
|
-
explicitMention: args.explicitMention,
|
|
20665
|
-
imageAttachmentCount: imageAttachmentCount > 0 ? imageAttachmentCount : void 0,
|
|
20666
|
-
slackTs,
|
|
20667
|
-
imagesHydrated: !messageHasPotentialImageAttachment
|
|
20668
|
-
}
|
|
20669
|
-
};
|
|
20681
|
+
for (const queued of args.queuedMessages ?? []) {
|
|
20682
|
+
const queuedMessage = toConversationMessage({
|
|
20683
|
+
entry: queued.message,
|
|
20684
|
+
explicitMention: queued.explicitMention,
|
|
20685
|
+
text: queued.userText
|
|
20686
|
+
});
|
|
20687
|
+
upsertConversationMessage(conversation, queuedMessage);
|
|
20688
|
+
}
|
|
20689
|
+
const incomingUserMessage = toConversationMessage({
|
|
20690
|
+
entry: args.message,
|
|
20691
|
+
explicitMention: args.explicitMention,
|
|
20692
|
+
text: args.text.userText
|
|
20693
|
+
});
|
|
20670
20694
|
const userMessageId = upsertConversationMessage(
|
|
20671
20695
|
conversation,
|
|
20672
20696
|
incomingUserMessage
|
|
20673
20697
|
);
|
|
20698
|
+
const messageHasPotentialImageAttachment = hasPotentialImageAttachment(args.message.attachments) || (args.queuedMessages ?? []).some(
|
|
20699
|
+
(queued) => hasPotentialImageAttachment(queued.message.attachments)
|
|
20700
|
+
);
|
|
20674
20701
|
const shouldHydrateVisionContext = !conversation.vision.backfillCompletedAtMs || messageHasPotentialImageAttachment || hasPendingImageHydration(conversation);
|
|
20675
20702
|
if (isVisionEnabled() && shouldHydrateVisionContext) {
|
|
20676
20703
|
await deps.hydrateConversationVisionContext(conversation, {
|
|
@@ -20757,28 +20784,20 @@ function createSlackRuntime(options) {
|
|
|
20757
20784
|
message,
|
|
20758
20785
|
decision,
|
|
20759
20786
|
completedAtMs,
|
|
20760
|
-
|
|
20787
|
+
text
|
|
20761
20788
|
}) => {
|
|
20762
20789
|
const conversation = coerceThreadConversationState(await thread.state);
|
|
20763
|
-
const
|
|
20764
|
-
|
|
20790
|
+
const conversationMessage = toConversationMessage({
|
|
20791
|
+
entry: message,
|
|
20792
|
+
explicitMention: Boolean(message.isMention),
|
|
20793
|
+
text: text.userText
|
|
20794
|
+
});
|
|
20765
20795
|
upsertConversationMessage(conversation, {
|
|
20766
|
-
|
|
20767
|
-
role: "user",
|
|
20768
|
-
text: normalizedUserText,
|
|
20769
|
-
createdAtMs: message.metadata.dateSent.getTime(),
|
|
20770
|
-
author: {
|
|
20771
|
-
userId: message.author.userId,
|
|
20772
|
-
userName: message.author.userName,
|
|
20773
|
-
fullName: message.author.fullName,
|
|
20774
|
-
isBot: typeof message.author.isBot === "boolean" ? message.author.isBot : void 0
|
|
20775
|
-
},
|
|
20796
|
+
...conversationMessage,
|
|
20776
20797
|
meta: {
|
|
20777
|
-
|
|
20778
|
-
slackTs,
|
|
20798
|
+
...conversationMessage.meta,
|
|
20779
20799
|
replied: false,
|
|
20780
|
-
skippedReason: decision.reason
|
|
20781
|
-
imagesHydrated: !hasPotentialImageAttachment(message.attachments)
|
|
20800
|
+
skippedReason: decision.reason
|
|
20782
20801
|
}
|
|
20783
20802
|
});
|
|
20784
20803
|
conversation.processing.activeTurnId = void 0;
|
|
@@ -21221,17 +21240,26 @@ function createProductionBot() {
|
|
|
21221
21240
|
});
|
|
21222
21241
|
}
|
|
21223
21242
|
function registerProductionHandlers(bot, slackRuntime) {
|
|
21224
|
-
bot.onNewMention((thread, message) => {
|
|
21243
|
+
bot.onNewMention((thread, message, context) => {
|
|
21225
21244
|
rehydrateAttachmentFetchers(message);
|
|
21226
|
-
|
|
21245
|
+
context?.skipped.forEach((skipped) => rehydrateAttachmentFetchers(skipped));
|
|
21246
|
+
return slackRuntime.handleNewMention(thread, message, {
|
|
21247
|
+
messageContext: context
|
|
21248
|
+
});
|
|
21227
21249
|
});
|
|
21228
|
-
bot.onDirectMessage((thread, message) => {
|
|
21250
|
+
bot.onDirectMessage((thread, message, _channel, context) => {
|
|
21229
21251
|
rehydrateAttachmentFetchers(message);
|
|
21230
|
-
|
|
21252
|
+
context?.skipped.forEach((skipped) => rehydrateAttachmentFetchers(skipped));
|
|
21253
|
+
return slackRuntime.handleNewMention(thread, message, {
|
|
21254
|
+
messageContext: context
|
|
21255
|
+
});
|
|
21231
21256
|
});
|
|
21232
|
-
bot.onSubscribedMessage((thread, message) => {
|
|
21257
|
+
bot.onSubscribedMessage((thread, message, context) => {
|
|
21233
21258
|
rehydrateAttachmentFetchers(message);
|
|
21234
|
-
|
|
21259
|
+
context?.skipped.forEach((skipped) => rehydrateAttachmentFetchers(skipped));
|
|
21260
|
+
return slackRuntime.handleSubscribedMessage(thread, message, {
|
|
21261
|
+
messageContext: context
|
|
21262
|
+
});
|
|
21235
21263
|
});
|
|
21236
21264
|
bot.onAssistantThreadStarted(
|
|
21237
21265
|
(event) => slackRuntime.handleAssistantThreadStarted(event)
|
|
@@ -21718,14 +21746,13 @@ async function createApp(options) {
|
|
|
21718
21746
|
}
|
|
21719
21747
|
await next();
|
|
21720
21748
|
});
|
|
21721
|
-
app.get("/", () =>
|
|
21722
|
-
app.get("/health", () =>
|
|
21723
|
-
app.get("/api/info", () => GET());
|
|
21749
|
+
app.get("/", () => GET());
|
|
21750
|
+
app.get("/health", () => GET());
|
|
21724
21751
|
app.get("/api/oauth/callback/mcp/:provider", (c) => {
|
|
21725
|
-
return
|
|
21752
|
+
return GET3(c.req.raw, c.req.param("provider"), waitUntil);
|
|
21726
21753
|
});
|
|
21727
21754
|
app.get("/api/oauth/callback/:provider", (c) => {
|
|
21728
|
-
return
|
|
21755
|
+
return GET4(c.req.raw, c.req.param("provider"), waitUntil);
|
|
21729
21756
|
});
|
|
21730
21757
|
app.post("/api/internal/turn-resume", (c) => {
|
|
21731
21758
|
return POST2(c.req.raw, waitUntil);
|
|
@@ -21734,7 +21761,7 @@ async function createApp(options) {
|
|
|
21734
21761
|
return POST(c.req.raw, waitUntil);
|
|
21735
21762
|
});
|
|
21736
21763
|
app.get("/api/internal/heartbeat", (c) => {
|
|
21737
|
-
return
|
|
21764
|
+
return GET2(c.req.raw, waitUntil);
|
|
21738
21765
|
});
|
|
21739
21766
|
app.post("/api/webhooks/:platform", (c) => {
|
|
21740
21767
|
return POST3(c.req.raw, c.req.param("platform"), waitUntil);
|