@sentry/junior 0.57.0 → 0.58.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 +1301 -1278
- 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;
|
|
@@ -14419,7 +14308,7 @@ async function syncIncompleteDispatchIndex(state, record) {
|
|
|
14419
14308
|
await state.set(
|
|
14420
14309
|
incompleteDispatchIndexKey(),
|
|
14421
14310
|
next.slice(-DISPATCH_INDEX_MAX_LENGTH),
|
|
14422
|
-
|
|
14311
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
14423
14312
|
);
|
|
14424
14313
|
});
|
|
14425
14314
|
}
|
|
@@ -14427,7 +14316,7 @@ async function putRecord(state, record) {
|
|
|
14427
14316
|
await state.set(
|
|
14428
14317
|
getDispatchStorageKey(record.id),
|
|
14429
14318
|
record,
|
|
14430
|
-
|
|
14319
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
14431
14320
|
);
|
|
14432
14321
|
await syncIncompleteDispatchIndex(state, record);
|
|
14433
14322
|
}
|
|
@@ -14541,7 +14430,6 @@ async function markDispatch(args) {
|
|
|
14541
14430
|
...current,
|
|
14542
14431
|
status: args.status,
|
|
14543
14432
|
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
14544
|
-
...typeof args.resumeCheckpointVersion === "number" ? { resumeCheckpointVersion: args.resumeCheckpointVersion } : {},
|
|
14545
14433
|
...args.resultMessageTs ? { resultMessageTs: args.resultMessageTs } : {}
|
|
14546
14434
|
});
|
|
14547
14435
|
});
|
|
@@ -14774,12 +14662,11 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
14774
14662
|
return;
|
|
14775
14663
|
}
|
|
14776
14664
|
if (isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
14777
|
-
const
|
|
14665
|
+
const version = error.metadata?.version;
|
|
14778
14666
|
const nextSliceId = error.metadata?.sliceId;
|
|
14779
|
-
if (typeof
|
|
14667
|
+
if (typeof version === "number" && canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
14780
14668
|
const awaiting = await markDispatch({
|
|
14781
14669
|
dispatch,
|
|
14782
|
-
resumeCheckpointVersion: checkpointVersion,
|
|
14783
14670
|
status: "awaiting_resume"
|
|
14784
14671
|
});
|
|
14785
14672
|
await scheduleCallback({
|
|
@@ -15125,7 +15012,7 @@ function verifyHeartbeatRequest(request) {
|
|
|
15125
15012
|
const expected = Buffer.from(secret);
|
|
15126
15013
|
return actual.length === expected.length && timingSafeEqual4(actual, expected);
|
|
15127
15014
|
}
|
|
15128
|
-
async function
|
|
15015
|
+
async function GET2(request, waitUntil) {
|
|
15129
15016
|
if (!verifyHeartbeatRequest(request)) {
|
|
15130
15017
|
return new Response("Unauthorized", { status: 401 });
|
|
15131
15018
|
}
|
|
@@ -15144,6 +15031,9 @@ async function GET4(request, waitUntil) {
|
|
|
15144
15031
|
return new Response("Accepted", { status: 202 });
|
|
15145
15032
|
}
|
|
15146
15033
|
|
|
15034
|
+
// src/handlers/mcp-oauth-callback.ts
|
|
15035
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
15036
|
+
|
|
15147
15037
|
// src/chat/runtime/delivered-turn-state.ts
|
|
15148
15038
|
function buildDeliveredTurnStatePatch(args) {
|
|
15149
15039
|
const conversation = structuredClone(args.conversation);
|
|
@@ -15490,7 +15380,7 @@ function createSlackAdapterStatusSender(args) {
|
|
|
15490
15380
|
};
|
|
15491
15381
|
}
|
|
15492
15382
|
function createSlackWebApiStatusSender(args) {
|
|
15493
|
-
const
|
|
15383
|
+
const getClient2 = args.getSlackClient ?? getSlackClient;
|
|
15494
15384
|
return async (text, loadingMessages) => {
|
|
15495
15385
|
const channelId = args.channelId;
|
|
15496
15386
|
const threadTs = args.threadTs;
|
|
@@ -15503,7 +15393,7 @@ function createSlackWebApiStatusSender(args) {
|
|
|
15503
15393
|
}
|
|
15504
15394
|
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
15505
15395
|
try {
|
|
15506
|
-
await
|
|
15396
|
+
await getClient2().assistant.threads.setStatus({
|
|
15507
15397
|
channel_id: normalizedChannelId,
|
|
15508
15398
|
thread_ts: threadTs,
|
|
15509
15399
|
status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
@@ -15649,52 +15539,6 @@ function getWorkspaceTeamId() {
|
|
|
15649
15539
|
return workspaceTeamIdStorage.getStore();
|
|
15650
15540
|
}
|
|
15651
15541
|
|
|
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
15542
|
// src/chat/runtime/thread-context.ts
|
|
15699
15543
|
function toSlackTeamId(value) {
|
|
15700
15544
|
const candidate = toOptionalString(value);
|
|
@@ -16070,7 +15914,7 @@ async function resumeSlackTurn(args) {
|
|
|
16070
15914
|
status.start();
|
|
16071
15915
|
const generateReply = runArgs.generateReply ?? generateAssistantReply;
|
|
16072
15916
|
const replyContext = createResumeReplyContext(runArgs, status);
|
|
16073
|
-
const
|
|
15917
|
+
const priorSessionRecord = replyContext.correlation?.conversationId && replyContext.correlation?.turnId ? await getAgentTurnSessionRecord(
|
|
16074
15918
|
replyContext.correlation.conversationId,
|
|
16075
15919
|
replyContext.correlation.turnId
|
|
16076
15920
|
) : void 0;
|
|
@@ -16097,10 +15941,10 @@ async function resumeSlackTurn(args) {
|
|
|
16097
15941
|
await status.stop();
|
|
16098
15942
|
const footer = buildSlackReplyFooter({
|
|
16099
15943
|
conversationId: runArgs.replyContext?.correlation?.conversationId ?? lockKey,
|
|
16100
|
-
durationMs: typeof
|
|
15944
|
+
durationMs: typeof priorSessionRecord?.cumulativeDurationMs === "number" || typeof reply.diagnostics.durationMs === "number" ? (priorSessionRecord?.cumulativeDurationMs ?? 0) + (reply.diagnostics.durationMs ?? 0) : void 0,
|
|
16101
15945
|
thinkingLevel: reply.diagnostics.thinkingLevel,
|
|
16102
15946
|
usage: addAgentTurnUsage(
|
|
16103
|
-
|
|
15947
|
+
priorSessionRecord?.cumulativeUsage,
|
|
16104
15948
|
reply.diagnostics.usage
|
|
16105
15949
|
) ?? reply.diagnostics.usage
|
|
16106
15950
|
});
|
|
@@ -16291,6 +16135,9 @@ var CALLBACK_PAGES = {
|
|
|
16291
16135
|
status: 500
|
|
16292
16136
|
}
|
|
16293
16137
|
};
|
|
16138
|
+
function mcpAuthorizationId(args) {
|
|
16139
|
+
return `${args.sessionId}:mcp:${args.provider}`;
|
|
16140
|
+
}
|
|
16294
16141
|
function htmlResponse(kind) {
|
|
16295
16142
|
const page = CALLBACK_PAGES[kind];
|
|
16296
16143
|
return htmlCallbackResponse(page.title, page.message, page.status);
|
|
@@ -16312,27 +16159,28 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
16312
16159
|
...statePatch
|
|
16313
16160
|
});
|
|
16314
16161
|
}
|
|
16315
|
-
async function
|
|
16162
|
+
async function failSessionRecordBestEffort(args) {
|
|
16316
16163
|
try {
|
|
16317
|
-
await
|
|
16164
|
+
await failAgentTurnSessionRecord({
|
|
16318
16165
|
conversationId: args.conversationId,
|
|
16319
16166
|
sessionId: args.sessionId,
|
|
16320
|
-
errorMessage: args.errorMessage
|
|
16167
|
+
errorMessage: args.errorMessage,
|
|
16168
|
+
expectedVersion: args.expectedVersion
|
|
16321
16169
|
});
|
|
16322
16170
|
} catch (error) {
|
|
16323
16171
|
logException(
|
|
16324
16172
|
error,
|
|
16325
|
-
"
|
|
16173
|
+
"mcp_oauth_callback_session_record_fail_persist_failed",
|
|
16326
16174
|
{},
|
|
16327
16175
|
{
|
|
16328
16176
|
"app.ai.conversation_id": args.conversationId,
|
|
16329
16177
|
"app.ai.session_id": args.sessionId
|
|
16330
16178
|
},
|
|
16331
|
-
"Failed to mark MCP OAuth-resumed turn
|
|
16179
|
+
"Failed to mark MCP OAuth-resumed turn session record failed"
|
|
16332
16180
|
);
|
|
16333
16181
|
}
|
|
16334
16182
|
}
|
|
16335
|
-
async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
16183
|
+
async function persistFailedReplyState(channelId, threadTs, sessionId, expectedVersion) {
|
|
16336
16184
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
16337
16185
|
const currentState = await getPersistedThreadState(threadId);
|
|
16338
16186
|
const conversation = coerceThreadConversationState(currentState);
|
|
@@ -16345,10 +16193,11 @@ async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
|
16345
16193
|
markConversationMessage,
|
|
16346
16194
|
updateConversationStats
|
|
16347
16195
|
});
|
|
16348
|
-
await
|
|
16196
|
+
await failSessionRecordBestEffort({
|
|
16349
16197
|
conversationId: threadId,
|
|
16350
16198
|
sessionId,
|
|
16351
|
-
errorMessage: "OAuth-resumed MCP turn failed"
|
|
16199
|
+
errorMessage: "OAuth-resumed MCP turn failed",
|
|
16200
|
+
expectedVersion
|
|
16352
16201
|
});
|
|
16353
16202
|
await persistThreadStateById(threadId, {
|
|
16354
16203
|
conversation
|
|
@@ -16374,10 +16223,10 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16374
16223
|
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
16375
16224
|
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
16376
16225
|
await persistThreadStateById(threadId, { conversation });
|
|
16377
|
-
await
|
|
16226
|
+
await abandonAgentTurnSessionRecord({
|
|
16378
16227
|
conversationId: authSession.conversationId,
|
|
16379
16228
|
sessionId: pendingAuth.sessionId,
|
|
16380
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16229
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16381
16230
|
});
|
|
16382
16231
|
return;
|
|
16383
16232
|
}
|
|
@@ -16414,10 +16263,10 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16414
16263
|
await persistThreadStateById(threadId, {
|
|
16415
16264
|
conversation: lockedConversation
|
|
16416
16265
|
});
|
|
16417
|
-
await
|
|
16266
|
+
await abandonAgentTurnSessionRecord({
|
|
16418
16267
|
conversationId: authSession.conversationId,
|
|
16419
16268
|
sessionId: lockedPendingAuth.sessionId,
|
|
16420
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16269
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16421
16270
|
});
|
|
16422
16271
|
return false;
|
|
16423
16272
|
}
|
|
@@ -16431,6 +16280,13 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16431
16280
|
if (!lockedUserMessage) {
|
|
16432
16281
|
return false;
|
|
16433
16282
|
}
|
|
16283
|
+
const lockedSessionRecord = await getAgentTurnSessionRecord(
|
|
16284
|
+
authSession.conversationId,
|
|
16285
|
+
lockedSessionId
|
|
16286
|
+
);
|
|
16287
|
+
if (!lockedSessionRecord || lockedSessionRecord.state !== "awaiting_resume" || lockedSessionRecord.resumeReason !== "auth") {
|
|
16288
|
+
return false;
|
|
16289
|
+
}
|
|
16434
16290
|
const lockedConversationContext = buildConversationContext(
|
|
16435
16291
|
lockedConversation,
|
|
16436
16292
|
{
|
|
@@ -16440,6 +16296,17 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16440
16296
|
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
16441
16297
|
authSession.channelId
|
|
16442
16298
|
);
|
|
16299
|
+
await recordAuthorizationCompleted({
|
|
16300
|
+
conversationId: authSession.conversationId,
|
|
16301
|
+
kind: "mcp",
|
|
16302
|
+
provider,
|
|
16303
|
+
requesterId: authSession.userId,
|
|
16304
|
+
authorizationId: mcpAuthorizationId({
|
|
16305
|
+
provider,
|
|
16306
|
+
sessionId: lockedSessionId
|
|
16307
|
+
}),
|
|
16308
|
+
ttlMs: THREAD_STATE_TTL_MS4
|
|
16309
|
+
});
|
|
16443
16310
|
return {
|
|
16444
16311
|
messageText: lockedUserMessage.text,
|
|
16445
16312
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
@@ -16485,8 +16352,9 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16485
16352
|
);
|
|
16486
16353
|
},
|
|
16487
16354
|
onPostDeliveryCommitFailure: async () => {
|
|
16488
|
-
await
|
|
16355
|
+
await failAgentTurnSessionRecord({
|
|
16489
16356
|
conversationId: authSession.conversationId,
|
|
16357
|
+
expectedVersion: lockedSessionRecord.version,
|
|
16490
16358
|
sessionId: lockedSessionId,
|
|
16491
16359
|
errorMessage: "OAuth-resumed MCP reply was delivered but completion state did not persist"
|
|
16492
16360
|
});
|
|
@@ -16496,7 +16364,8 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16496
16364
|
await persistFailedReplyState(
|
|
16497
16365
|
authSession.channelId,
|
|
16498
16366
|
authSession.threadTs,
|
|
16499
|
-
lockedSessionId
|
|
16367
|
+
lockedSessionId,
|
|
16368
|
+
lockedSessionRecord.version
|
|
16500
16369
|
);
|
|
16501
16370
|
} catch (persistError) {
|
|
16502
16371
|
logException(
|
|
@@ -16527,11 +16396,11 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16527
16396
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
16528
16397
|
throw error;
|
|
16529
16398
|
}
|
|
16530
|
-
const
|
|
16399
|
+
const version = error.metadata?.version;
|
|
16531
16400
|
const nextSliceId = error.metadata?.sliceId;
|
|
16532
|
-
if (typeof
|
|
16401
|
+
if (typeof version !== "number") {
|
|
16533
16402
|
throw new Error(
|
|
16534
|
-
"Timed-out MCP resume did not include a
|
|
16403
|
+
"Timed-out MCP resume did not include a turn-session version"
|
|
16535
16404
|
);
|
|
16536
16405
|
}
|
|
16537
16406
|
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
@@ -16551,14 +16420,14 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16551
16420
|
await scheduleTurnTimeoutResume({
|
|
16552
16421
|
conversationId: authSession.conversationId,
|
|
16553
16422
|
sessionId: lockedSessionId,
|
|
16554
|
-
|
|
16423
|
+
expectedVersion: version
|
|
16555
16424
|
});
|
|
16556
16425
|
}
|
|
16557
16426
|
};
|
|
16558
16427
|
}
|
|
16559
16428
|
});
|
|
16560
16429
|
}
|
|
16561
|
-
async function
|
|
16430
|
+
async function GET3(request, provider, waitUntil) {
|
|
16562
16431
|
const url = new URL(request.url);
|
|
16563
16432
|
const state = url.searchParams.get("state")?.trim();
|
|
16564
16433
|
const code = url.searchParams.get("code")?.trim();
|
|
@@ -16604,9 +16473,12 @@ async function GET5(request, provider, waitUntil) {
|
|
|
16604
16473
|
}
|
|
16605
16474
|
}
|
|
16606
16475
|
|
|
16476
|
+
// src/handlers/oauth-callback.ts
|
|
16477
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS5 } from "chat";
|
|
16478
|
+
|
|
16607
16479
|
// src/chat/slack/app-home.ts
|
|
16608
16480
|
import fs5 from "fs";
|
|
16609
|
-
import
|
|
16481
|
+
import path9 from "path";
|
|
16610
16482
|
var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
|
|
16611
16483
|
var MAX_HOME_SKILLS = 6;
|
|
16612
16484
|
var MAX_SECTION_TEXT_CHARS = 3e3;
|
|
@@ -16618,7 +16490,7 @@ function clampSectionText(text) {
|
|
|
16618
16490
|
return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
|
|
16619
16491
|
}
|
|
16620
16492
|
function loadDescriptionText() {
|
|
16621
|
-
const descriptionPath =
|
|
16493
|
+
const descriptionPath = path9.join(homeDir(), "DESCRIPTION.md");
|
|
16622
16494
|
try {
|
|
16623
16495
|
const raw = fs5.readFileSync(descriptionPath, "utf8").trim();
|
|
16624
16496
|
if (raw.length > 0) {
|
|
@@ -16769,23 +16641,27 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
16769
16641
|
...statePatch
|
|
16770
16642
|
});
|
|
16771
16643
|
}
|
|
16772
|
-
|
|
16644
|
+
function pluginAuthorizationId(args) {
|
|
16645
|
+
return `${args.sessionId}:plugin:${args.provider}`;
|
|
16646
|
+
}
|
|
16647
|
+
async function failSessionRecordBestEffort2(args) {
|
|
16773
16648
|
try {
|
|
16774
|
-
await
|
|
16649
|
+
await failAgentTurnSessionRecord({
|
|
16775
16650
|
conversationId: args.conversationId,
|
|
16651
|
+
expectedVersion: args.expectedVersion,
|
|
16776
16652
|
sessionId: args.sessionId,
|
|
16777
16653
|
errorMessage: args.errorMessage
|
|
16778
16654
|
});
|
|
16779
16655
|
} catch (error) {
|
|
16780
16656
|
logException(
|
|
16781
16657
|
error,
|
|
16782
|
-
"
|
|
16658
|
+
"oauth_callback_session_record_fail_persist_failed",
|
|
16783
16659
|
{},
|
|
16784
16660
|
{
|
|
16785
16661
|
"app.ai.conversation_id": args.conversationId,
|
|
16786
16662
|
"app.ai.session_id": args.sessionId
|
|
16787
16663
|
},
|
|
16788
|
-
"Failed to mark OAuth-resumed turn
|
|
16664
|
+
"Failed to mark OAuth-resumed turn session record failed"
|
|
16789
16665
|
);
|
|
16790
16666
|
}
|
|
16791
16667
|
}
|
|
@@ -16801,8 +16677,9 @@ async function persistFailedOAuthReplyState(args) {
|
|
|
16801
16677
|
markConversationMessage,
|
|
16802
16678
|
updateConversationStats
|
|
16803
16679
|
});
|
|
16804
|
-
await
|
|
16680
|
+
await failSessionRecordBestEffort2({
|
|
16805
16681
|
conversationId: args.conversationId,
|
|
16682
|
+
expectedVersion: args.expectedVersion,
|
|
16806
16683
|
sessionId: args.sessionId,
|
|
16807
16684
|
errorMessage: "OAuth-resumed turn failed"
|
|
16808
16685
|
});
|
|
@@ -16810,21 +16687,21 @@ async function persistFailedOAuthReplyState(args) {
|
|
|
16810
16687
|
conversation
|
|
16811
16688
|
});
|
|
16812
16689
|
}
|
|
16813
|
-
async function
|
|
16690
|
+
async function resumeOAuthSessionRecordTurn(stored) {
|
|
16814
16691
|
if (!stored.resumeConversationId || !stored.resumeSessionId || !stored.channelId || !stored.threadTs) {
|
|
16815
16692
|
return false;
|
|
16816
16693
|
}
|
|
16817
|
-
const
|
|
16694
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
16818
16695
|
stored.resumeConversationId,
|
|
16819
16696
|
stored.resumeSessionId
|
|
16820
16697
|
);
|
|
16821
|
-
if (!
|
|
16698
|
+
if (!sessionRecord) {
|
|
16822
16699
|
return false;
|
|
16823
16700
|
}
|
|
16824
|
-
if (
|
|
16701
|
+
if (sessionRecord.state === "completed" || sessionRecord.state === "failed" || sessionRecord.state === "abandoned") {
|
|
16825
16702
|
return true;
|
|
16826
16703
|
}
|
|
16827
|
-
if (
|
|
16704
|
+
if (sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "auth") {
|
|
16828
16705
|
return true;
|
|
16829
16706
|
}
|
|
16830
16707
|
const currentState = await getPersistedThreadState(
|
|
@@ -16845,10 +16722,10 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16845
16722
|
await persistThreadStateById(stored.resumeConversationId, {
|
|
16846
16723
|
conversation
|
|
16847
16724
|
});
|
|
16848
|
-
await
|
|
16725
|
+
await abandonAgentTurnSessionRecord({
|
|
16849
16726
|
conversationId: stored.resumeConversationId,
|
|
16850
16727
|
sessionId: pendingAuth.sessionId,
|
|
16851
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16728
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16852
16729
|
});
|
|
16853
16730
|
return true;
|
|
16854
16731
|
}
|
|
@@ -16871,11 +16748,11 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16871
16748
|
lockKey: stored.resumeConversationId,
|
|
16872
16749
|
initialText: "",
|
|
16873
16750
|
beforeStart: async () => {
|
|
16874
|
-
const
|
|
16751
|
+
const lockedSessionRecord = await getAgentTurnSessionRecord(
|
|
16875
16752
|
stored.resumeConversationId,
|
|
16876
16753
|
stored.resumeSessionId
|
|
16877
16754
|
);
|
|
16878
|
-
if (!
|
|
16755
|
+
if (!lockedSessionRecord || lockedSessionRecord.state !== "awaiting_resume" || lockedSessionRecord.resumeReason !== "auth") {
|
|
16879
16756
|
return false;
|
|
16880
16757
|
}
|
|
16881
16758
|
const lockedState = await getPersistedThreadState(
|
|
@@ -16899,10 +16776,10 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16899
16776
|
await persistThreadStateById(stored.resumeConversationId, {
|
|
16900
16777
|
conversation: lockedConversation
|
|
16901
16778
|
});
|
|
16902
|
-
await
|
|
16779
|
+
await abandonAgentTurnSessionRecord({
|
|
16903
16780
|
conversationId: stored.resumeConversationId,
|
|
16904
16781
|
sessionId: lockedPendingAuth.sessionId,
|
|
16905
|
-
errorMessage: "Auth completed after a newer thread message
|
|
16782
|
+
errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
|
|
16906
16783
|
});
|
|
16907
16784
|
return false;
|
|
16908
16785
|
}
|
|
@@ -16925,6 +16802,17 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16925
16802
|
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
16926
16803
|
stored.channelId
|
|
16927
16804
|
);
|
|
16805
|
+
await recordAuthorizationCompleted({
|
|
16806
|
+
conversationId: stored.resumeConversationId,
|
|
16807
|
+
kind: "plugin",
|
|
16808
|
+
provider: stored.provider,
|
|
16809
|
+
requesterId: stored.userId,
|
|
16810
|
+
authorizationId: pluginAuthorizationId({
|
|
16811
|
+
provider: stored.provider,
|
|
16812
|
+
sessionId: lockedSessionId
|
|
16813
|
+
}),
|
|
16814
|
+
ttlMs: THREAD_STATE_TTL_MS5
|
|
16815
|
+
});
|
|
16928
16816
|
return {
|
|
16929
16817
|
messageText: stored.pendingMessage ?? lockedUserMessage.text,
|
|
16930
16818
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
@@ -16969,7 +16857,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16969
16857
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
16970
16858
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
16971
16859
|
},
|
|
16972
|
-
"OAuth callback auto-resumed
|
|
16860
|
+
"OAuth callback auto-resumed session record finished replying"
|
|
16973
16861
|
);
|
|
16974
16862
|
await persistCompletedOAuthReplyState({
|
|
16975
16863
|
conversationId: stored.resumeConversationId,
|
|
@@ -16978,9 +16866,9 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16978
16866
|
});
|
|
16979
16867
|
},
|
|
16980
16868
|
onPostDeliveryCommitFailure: async () => {
|
|
16981
|
-
await
|
|
16869
|
+
await failAgentTurnSessionRecord({
|
|
16982
16870
|
conversationId: stored.resumeConversationId,
|
|
16983
|
-
|
|
16871
|
+
expectedVersion: lockedSessionRecord.version,
|
|
16984
16872
|
sessionId: lockedSessionId,
|
|
16985
16873
|
errorMessage: "OAuth-resumed reply was delivered but completion state did not persist"
|
|
16986
16874
|
});
|
|
@@ -16988,6 +16876,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
16988
16876
|
onFailure: async () => {
|
|
16989
16877
|
await persistFailedOAuthReplyState({
|
|
16990
16878
|
conversationId: stored.resumeConversationId,
|
|
16879
|
+
expectedVersion: lockedSessionRecord.version,
|
|
16991
16880
|
sessionId: lockedSessionId
|
|
16992
16881
|
});
|
|
16993
16882
|
},
|
|
@@ -17001,11 +16890,11 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
17001
16890
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
17002
16891
|
throw error;
|
|
17003
16892
|
}
|
|
17004
|
-
const
|
|
16893
|
+
const version = error.metadata?.version;
|
|
17005
16894
|
const nextSliceId = error.metadata?.sliceId;
|
|
17006
|
-
if (typeof
|
|
16895
|
+
if (typeof version !== "number") {
|
|
17007
16896
|
throw new Error(
|
|
17008
|
-
"Timed-out OAuth resume did not include a
|
|
16897
|
+
"Timed-out OAuth resume did not include a turn-session version"
|
|
17009
16898
|
);
|
|
17010
16899
|
}
|
|
17011
16900
|
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
@@ -17016,7 +16905,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
17016
16905
|
await scheduleTurnTimeoutResume({
|
|
17017
16906
|
conversationId: stored.resumeConversationId,
|
|
17018
16907
|
sessionId: lockedSessionId,
|
|
17019
|
-
|
|
16908
|
+
expectedVersion: version
|
|
17020
16909
|
});
|
|
17021
16910
|
}
|
|
17022
16911
|
};
|
|
@@ -17060,7 +16949,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
17060
16949
|
}
|
|
17061
16950
|
});
|
|
17062
16951
|
}
|
|
17063
|
-
async function
|
|
16952
|
+
async function GET4(request, provider, waitUntil) {
|
|
17064
16953
|
const providerConfig = getPluginOAuthConfig(provider);
|
|
17065
16954
|
if (!providerConfig) {
|
|
17066
16955
|
return htmlErrorResponse(
|
|
@@ -17199,7 +17088,7 @@ async function GET6(request, provider, waitUntil) {
|
|
|
17199
17088
|
if (stored.pendingMessage && stored.channelId && stored.threadTs) {
|
|
17200
17089
|
waitUntil(async () => {
|
|
17201
17090
|
try {
|
|
17202
|
-
const resumed = await
|
|
17091
|
+
const resumed = await resumeOAuthSessionRecordTurn(stored);
|
|
17203
17092
|
if (!resumed) {
|
|
17204
17093
|
await resumePendingOAuthMessage(stored);
|
|
17205
17094
|
}
|
|
@@ -17431,12 +17320,12 @@ function normalizePort(value) {
|
|
|
17431
17320
|
function sandboxIdFromPayload(payload) {
|
|
17432
17321
|
return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
|
|
17433
17322
|
}
|
|
17434
|
-
function normalizedForwardedPath(
|
|
17435
|
-
if (!
|
|
17323
|
+
function normalizedForwardedPath(path10) {
|
|
17324
|
+
if (!path10.startsWith("/") || path10.startsWith("//") || path10.includes("#") || /[\r\n]/.test(path10)) {
|
|
17436
17325
|
return { ok: false, error: "Invalid forwarded path" };
|
|
17437
17326
|
}
|
|
17438
17327
|
try {
|
|
17439
|
-
const url = new URL(
|
|
17328
|
+
const url = new URL(path10, "https://sandbox-forwarded.local");
|
|
17440
17329
|
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
17441
17330
|
} catch {
|
|
17442
17331
|
return { ok: false, error: "Invalid forwarded path" };
|
|
@@ -17471,13 +17360,13 @@ function buildUpstreamUrl(request) {
|
|
|
17471
17360
|
if (forwardedPort && !port) {
|
|
17472
17361
|
return { ok: false, error: "Invalid forwarded port" };
|
|
17473
17362
|
}
|
|
17474
|
-
const
|
|
17475
|
-
if (!
|
|
17476
|
-
return { ok: false, error:
|
|
17363
|
+
const path10 = upstreamPath(request);
|
|
17364
|
+
if (!path10.ok) {
|
|
17365
|
+
return { ok: false, error: path10.error };
|
|
17477
17366
|
}
|
|
17478
17367
|
try {
|
|
17479
17368
|
const url = new URL(
|
|
17480
|
-
`${scheme}://${host}${port ? `:${port}` : ""}${
|
|
17369
|
+
`${scheme}://${host}${port ? `:${port}` : ""}${path10.path}`
|
|
17481
17370
|
);
|
|
17482
17371
|
return { ok: true, url };
|
|
17483
17372
|
} catch {
|
|
@@ -17794,63 +17683,65 @@ function sleep4(ms) {
|
|
|
17794
17683
|
}
|
|
17795
17684
|
async function persistCompletedReplyState2(args) {
|
|
17796
17685
|
const currentState = await getPersistedThreadState(
|
|
17797
|
-
args.
|
|
17686
|
+
args.sessionRecord.conversationId
|
|
17798
17687
|
);
|
|
17799
17688
|
const conversation = coerceThreadConversationState(currentState);
|
|
17800
17689
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
17801
17690
|
const userMessage2 = getTurnUserMessage(
|
|
17802
17691
|
conversation,
|
|
17803
|
-
args.
|
|
17692
|
+
args.sessionRecord.sessionId
|
|
17804
17693
|
);
|
|
17805
17694
|
const statePatch = buildDeliveredTurnStatePatch({
|
|
17806
17695
|
artifacts,
|
|
17807
17696
|
conversation,
|
|
17808
17697
|
reply: args.reply,
|
|
17809
|
-
sessionId: args.
|
|
17698
|
+
sessionId: args.sessionRecord.sessionId,
|
|
17810
17699
|
userMessageId: userMessage2?.id
|
|
17811
17700
|
});
|
|
17812
|
-
await persistThreadStateById(args.
|
|
17701
|
+
await persistThreadStateById(args.sessionRecord.conversationId, {
|
|
17813
17702
|
...statePatch
|
|
17814
17703
|
});
|
|
17815
17704
|
}
|
|
17816
|
-
async function
|
|
17705
|
+
async function failSessionRecordBestEffort3(args) {
|
|
17817
17706
|
try {
|
|
17818
|
-
await
|
|
17819
|
-
conversationId: args.
|
|
17820
|
-
|
|
17821
|
-
sessionId: args.
|
|
17707
|
+
await failAgentTurnSessionRecord({
|
|
17708
|
+
conversationId: args.sessionRecord.conversationId,
|
|
17709
|
+
expectedVersion: args.sessionRecord.version,
|
|
17710
|
+
sessionId: args.sessionRecord.sessionId,
|
|
17822
17711
|
errorMessage: args.errorMessage
|
|
17823
17712
|
});
|
|
17824
17713
|
} catch (error) {
|
|
17825
17714
|
logException(
|
|
17826
17715
|
error,
|
|
17827
|
-
"
|
|
17716
|
+
"timeout_resume_session_record_fail_persist_failed",
|
|
17828
17717
|
{},
|
|
17829
17718
|
{
|
|
17830
|
-
"app.ai.conversation_id": args.
|
|
17831
|
-
"app.ai.session_id": args.
|
|
17719
|
+
"app.ai.conversation_id": args.sessionRecord.conversationId,
|
|
17720
|
+
"app.ai.session_id": args.sessionRecord.sessionId
|
|
17832
17721
|
},
|
|
17833
|
-
"Failed to mark timed-out turn
|
|
17722
|
+
"Failed to mark timed-out turn session record failed"
|
|
17834
17723
|
);
|
|
17835
17724
|
}
|
|
17836
17725
|
}
|
|
17837
|
-
async function persistFailedReplyState2(
|
|
17838
|
-
const currentState = await getPersistedThreadState(
|
|
17726
|
+
async function persistFailedReplyState2(sessionRecord) {
|
|
17727
|
+
const currentState = await getPersistedThreadState(
|
|
17728
|
+
sessionRecord.conversationId
|
|
17729
|
+
);
|
|
17839
17730
|
const conversation = coerceThreadConversationState(currentState);
|
|
17840
|
-
clearPendingAuth(conversation,
|
|
17731
|
+
clearPendingAuth(conversation, sessionRecord.sessionId);
|
|
17841
17732
|
markTurnFailed({
|
|
17842
17733
|
conversation,
|
|
17843
17734
|
nowMs: Date.now(),
|
|
17844
|
-
sessionId:
|
|
17845
|
-
userMessageId: getTurnUserMessage(conversation,
|
|
17735
|
+
sessionId: sessionRecord.sessionId,
|
|
17736
|
+
userMessageId: getTurnUserMessage(conversation, sessionRecord.sessionId)?.id,
|
|
17846
17737
|
markConversationMessage,
|
|
17847
17738
|
updateConversationStats
|
|
17848
17739
|
});
|
|
17849
|
-
await
|
|
17850
|
-
|
|
17740
|
+
await failSessionRecordBestEffort3({
|
|
17741
|
+
sessionRecord,
|
|
17851
17742
|
errorMessage: "Timed-out turn failed while resuming"
|
|
17852
17743
|
});
|
|
17853
|
-
await persistThreadStateById(
|
|
17744
|
+
await persistThreadStateById(sessionRecord.conversationId, {
|
|
17854
17745
|
conversation
|
|
17855
17746
|
});
|
|
17856
17747
|
}
|
|
@@ -17867,11 +17758,11 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17867
17758
|
threadTs: thread.threadTs,
|
|
17868
17759
|
lockKey: payload.conversationId,
|
|
17869
17760
|
beforeStart: async () => {
|
|
17870
|
-
const
|
|
17761
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
17871
17762
|
payload.conversationId,
|
|
17872
17763
|
payload.sessionId
|
|
17873
17764
|
);
|
|
17874
|
-
if (!
|
|
17765
|
+
if (!sessionRecord || sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "timeout" || sessionRecord.version !== payload.expectedVersion) {
|
|
17875
17766
|
return false;
|
|
17876
17767
|
}
|
|
17877
17768
|
const currentState = await getPersistedThreadState(
|
|
@@ -17931,16 +17822,16 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17931
17822
|
...getTurnUserReplyAttachmentContext(userMessage2)
|
|
17932
17823
|
},
|
|
17933
17824
|
onSuccess: async (reply) => {
|
|
17934
|
-
await persistCompletedReplyState2({
|
|
17825
|
+
await persistCompletedReplyState2({ sessionRecord, reply });
|
|
17935
17826
|
},
|
|
17936
17827
|
onFailure: async () => {
|
|
17937
|
-
await persistFailedReplyState2(
|
|
17828
|
+
await persistFailedReplyState2(sessionRecord);
|
|
17938
17829
|
},
|
|
17939
17830
|
onPostDeliveryCommitFailure: async () => {
|
|
17940
|
-
await
|
|
17941
|
-
conversationId:
|
|
17942
|
-
|
|
17943
|
-
sessionId:
|
|
17831
|
+
await failAgentTurnSessionRecord({
|
|
17832
|
+
conversationId: sessionRecord.conversationId,
|
|
17833
|
+
expectedVersion: sessionRecord.version,
|
|
17834
|
+
sessionId: sessionRecord.sessionId,
|
|
17944
17835
|
errorMessage: "Timed-out turn reply was delivered but completion state did not persist"
|
|
17945
17836
|
});
|
|
17946
17837
|
},
|
|
@@ -17963,11 +17854,11 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17963
17854
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
17964
17855
|
throw error;
|
|
17965
17856
|
}
|
|
17966
|
-
const
|
|
17857
|
+
const version = error.metadata?.version;
|
|
17967
17858
|
const nextSliceId = error.metadata?.sliceId;
|
|
17968
|
-
if (typeof
|
|
17859
|
+
if (typeof version !== "number") {
|
|
17969
17860
|
throw new Error(
|
|
17970
|
-
"Timed-out resume turn did not include a
|
|
17861
|
+
"Timed-out resume turn did not include a turn-session version"
|
|
17971
17862
|
);
|
|
17972
17863
|
}
|
|
17973
17864
|
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
@@ -17988,7 +17879,7 @@ async function resumeTimedOutTurn(payload) {
|
|
|
17988
17879
|
await scheduleTurnTimeoutResume({
|
|
17989
17880
|
conversationId: payload.conversationId,
|
|
17990
17881
|
sessionId: payload.sessionId,
|
|
17991
|
-
|
|
17882
|
+
expectedVersion: version
|
|
17992
17883
|
});
|
|
17993
17884
|
}
|
|
17994
17885
|
};
|
|
@@ -18383,6 +18274,26 @@ async function decideSubscribedThreadReply(args) {
|
|
|
18383
18274
|
}
|
|
18384
18275
|
}
|
|
18385
18276
|
|
|
18277
|
+
// src/chat/runtime/turn-input.ts
|
|
18278
|
+
function combineTextParts(queuedTexts, latestText) {
|
|
18279
|
+
const parts = [...queuedTexts, latestText].filter(
|
|
18280
|
+
(part) => part.trim().length > 0
|
|
18281
|
+
);
|
|
18282
|
+
return parts.length > 0 ? parts.join("\n\n") : latestText;
|
|
18283
|
+
}
|
|
18284
|
+
function combineTurnText(queuedMessages, latestText) {
|
|
18285
|
+
return {
|
|
18286
|
+
rawText: combineTextParts(
|
|
18287
|
+
queuedMessages.map((message) => message.rawText),
|
|
18288
|
+
latestText.rawText
|
|
18289
|
+
),
|
|
18290
|
+
userText: combineTextParts(
|
|
18291
|
+
queuedMessages.map((message) => message.userText),
|
|
18292
|
+
latestText.userText
|
|
18293
|
+
)
|
|
18294
|
+
};
|
|
18295
|
+
}
|
|
18296
|
+
|
|
18386
18297
|
// src/chat/runtime/slack-runtime.ts
|
|
18387
18298
|
var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
|
|
18388
18299
|
async function maybeHandleThreadOptOutDecision(args) {
|
|
@@ -18394,6 +18305,19 @@ async function maybeHandleThreadOptOutDecision(args) {
|
|
|
18394
18305
|
await args.thread.post(THREAD_OPTOUT_ACK);
|
|
18395
18306
|
return true;
|
|
18396
18307
|
}
|
|
18308
|
+
function getQueuedMessages(context, options) {
|
|
18309
|
+
return (context?.skipped ?? []).map((message) => {
|
|
18310
|
+
const stripped = options.stripLeadingBotMention(message.text, {
|
|
18311
|
+
stripLeadingSlackMentionToken: options.explicitMention || Boolean(message.isMention)
|
|
18312
|
+
});
|
|
18313
|
+
return {
|
|
18314
|
+
explicitMention: options.explicitMention || Boolean(message.isMention),
|
|
18315
|
+
message,
|
|
18316
|
+
rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
|
|
18317
|
+
userText: appendSlackLegacyAttachmentText(stripped, message.raw)
|
|
18318
|
+
};
|
|
18319
|
+
});
|
|
18320
|
+
}
|
|
18397
18321
|
function buildLogContext(deps, args) {
|
|
18398
18322
|
return {
|
|
18399
18323
|
conversationId: args.threadId ?? args.runId,
|
|
@@ -18463,7 +18387,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18463
18387
|
message: args.message,
|
|
18464
18388
|
decision: args.decision,
|
|
18465
18389
|
completedAtMs,
|
|
18466
|
-
|
|
18390
|
+
text: args.text
|
|
18467
18391
|
});
|
|
18468
18392
|
}
|
|
18469
18393
|
};
|
|
@@ -18493,9 +18417,14 @@ function createSlackTurnRuntime(deps) {
|
|
|
18493
18417
|
);
|
|
18494
18418
|
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
18495
18419
|
await thread.subscribe();
|
|
18420
|
+
const queuedMessages = getQueuedMessages(hooks?.messageContext, {
|
|
18421
|
+
explicitMention: true,
|
|
18422
|
+
stripLeadingBotMention: deps.stripLeadingBotMention
|
|
18423
|
+
});
|
|
18496
18424
|
await deps.replyToThread(thread, message, {
|
|
18497
18425
|
explicitMention: true,
|
|
18498
18426
|
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
18427
|
+
queuedMessages,
|
|
18499
18428
|
onToolInvocation: toolInvocationHook
|
|
18500
18429
|
});
|
|
18501
18430
|
});
|
|
@@ -18558,28 +18487,33 @@ function createSlackTurnRuntime(deps) {
|
|
|
18558
18487
|
const legacyAttachmentText = renderSlackLegacyAttachmentText(
|
|
18559
18488
|
message.raw
|
|
18560
18489
|
);
|
|
18561
|
-
const rawUserText = appendSlackLegacyAttachmentText(
|
|
18562
|
-
message.text,
|
|
18563
|
-
message.raw
|
|
18564
|
-
);
|
|
18565
18490
|
const strippedUserText = deps.stripLeadingBotMention(message.text, {
|
|
18566
18491
|
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
18567
18492
|
});
|
|
18568
|
-
const
|
|
18569
|
-
|
|
18570
|
-
|
|
18571
|
-
|
|
18493
|
+
const currentText = {
|
|
18494
|
+
rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
|
|
18495
|
+
userText: appendSlackLegacyAttachmentText(
|
|
18496
|
+
strippedUserText,
|
|
18497
|
+
message.raw
|
|
18498
|
+
)
|
|
18499
|
+
};
|
|
18572
18500
|
const threadContext = {
|
|
18573
18501
|
threadId,
|
|
18574
18502
|
requesterId: message.author.userId,
|
|
18575
18503
|
channelId,
|
|
18576
18504
|
runId
|
|
18577
18505
|
};
|
|
18506
|
+
const queuedMessages = getQueuedMessages(hooks?.messageContext, {
|
|
18507
|
+
explicitMention: Boolean(message.isMention),
|
|
18508
|
+
stripLeadingBotMention: deps.stripLeadingBotMention
|
|
18509
|
+
});
|
|
18510
|
+
const combinedText = combineTurnText(queuedMessages, currentText);
|
|
18511
|
+
const turnIsExplicitMention = Boolean(message.isMention) || queuedMessages.some((queued) => queued.explicitMention);
|
|
18578
18512
|
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
18579
18513
|
botUserName: deps.assistantUserName,
|
|
18580
|
-
rawText:
|
|
18581
|
-
text: userText,
|
|
18582
|
-
isExplicitMention:
|
|
18514
|
+
rawText: combinedText.rawText,
|
|
18515
|
+
text: combinedText.userText,
|
|
18516
|
+
isExplicitMention: turnIsExplicitMention
|
|
18583
18517
|
});
|
|
18584
18518
|
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
18585
18519
|
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
@@ -18588,27 +18522,30 @@ function createSlackTurnRuntime(deps) {
|
|
|
18588
18522
|
message,
|
|
18589
18523
|
decision: { shouldReply: false, reason },
|
|
18590
18524
|
context: threadContext,
|
|
18591
|
-
|
|
18525
|
+
text: combinedText
|
|
18592
18526
|
});
|
|
18593
18527
|
return;
|
|
18594
18528
|
}
|
|
18595
18529
|
const preparedState = await deps.prepareTurnState({
|
|
18596
18530
|
thread,
|
|
18597
18531
|
message,
|
|
18598
|
-
|
|
18532
|
+
text: currentText,
|
|
18599
18533
|
explicitMention: Boolean(message.isMention),
|
|
18600
|
-
context: threadContext
|
|
18534
|
+
context: threadContext,
|
|
18535
|
+
queuedMessages
|
|
18601
18536
|
});
|
|
18602
18537
|
await deps.persistPreparedState({
|
|
18603
18538
|
thread,
|
|
18604
18539
|
preparedState
|
|
18605
18540
|
});
|
|
18606
18541
|
const decision = await deps.decideSubscribedReply({
|
|
18607
|
-
rawText:
|
|
18608
|
-
text: userText,
|
|
18542
|
+
rawText: combinedText.rawText,
|
|
18543
|
+
text: combinedText.userText,
|
|
18609
18544
|
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
18610
|
-
hasAttachments: message.attachments.length > 0 ||
|
|
18611
|
-
|
|
18545
|
+
hasAttachments: message.attachments.length > 0 || queuedMessages.some(
|
|
18546
|
+
(queued) => queued.message.attachments.length > 0
|
|
18547
|
+
) || legacyAttachmentText !== "",
|
|
18548
|
+
isExplicitMention: turnIsExplicitMention,
|
|
18612
18549
|
context: threadContext
|
|
18613
18550
|
});
|
|
18614
18551
|
if (await maybeHandleThreadOptOutDecision({
|
|
@@ -18622,7 +18559,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18622
18559
|
decision,
|
|
18623
18560
|
context: threadContext,
|
|
18624
18561
|
preparedState,
|
|
18625
|
-
|
|
18562
|
+
text: combinedText
|
|
18626
18563
|
});
|
|
18627
18564
|
return;
|
|
18628
18565
|
}
|
|
@@ -18633,7 +18570,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18633
18570
|
decision,
|
|
18634
18571
|
context: threadContext,
|
|
18635
18572
|
preparedState,
|
|
18636
|
-
|
|
18573
|
+
text: combinedText
|
|
18637
18574
|
});
|
|
18638
18575
|
return;
|
|
18639
18576
|
}
|
|
@@ -18651,6 +18588,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18651
18588
|
explicitMention: Boolean(message.isMention),
|
|
18652
18589
|
preparedState,
|
|
18653
18590
|
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
18591
|
+
queuedMessages,
|
|
18654
18592
|
onToolInvocation: toolInvocationHook
|
|
18655
18593
|
});
|
|
18656
18594
|
});
|
|
@@ -18748,6 +18686,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
18748
18686
|
}
|
|
18749
18687
|
|
|
18750
18688
|
// src/chat/services/context-compaction.ts
|
|
18689
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS6 } from "chat";
|
|
18751
18690
|
import {
|
|
18752
18691
|
estimateContextTokens,
|
|
18753
18692
|
estimateTokens
|
|
@@ -18930,22 +18869,8 @@ function buildReplacementHistory(args) {
|
|
|
18930
18869
|
${args.summary}`)
|
|
18931
18870
|
];
|
|
18932
18871
|
}
|
|
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) {
|
|
18872
|
+
function loadCompactionSource(messages) {
|
|
18873
|
+
if (messages.length > 0) {
|
|
18949
18874
|
return {
|
|
18950
18875
|
estimatedTokens: estimateHistoryTokens(messages),
|
|
18951
18876
|
messages
|
|
@@ -18954,10 +18879,7 @@ async function loadCompactionSource(args) {
|
|
|
18954
18879
|
return { reason: "missing_context" };
|
|
18955
18880
|
}
|
|
18956
18881
|
async function maybeCompactWithDeps(args, deps) {
|
|
18957
|
-
const source =
|
|
18958
|
-
conversationId: args.conversationId,
|
|
18959
|
-
previousSessionId: args.previousSessionId
|
|
18960
|
-
});
|
|
18882
|
+
const source = loadCompactionSource(args.piMessages);
|
|
18961
18883
|
if ("reason" in source) {
|
|
18962
18884
|
return { compacted: false, reason: source.reason };
|
|
18963
18885
|
}
|
|
@@ -19004,29 +18926,22 @@ async function writeCompactedThreadContext(args, sourceMessages, summary, contex
|
|
|
19004
18926
|
messages: trimTrailingAssistantMessages(sourceMessages),
|
|
19005
18927
|
summary
|
|
19006
18928
|
});
|
|
19007
|
-
|
|
19008
|
-
await upsertAgentTurnSessionCheckpoint({
|
|
18929
|
+
await commitMessages({
|
|
19009
18930
|
conversationId: args.conversationId,
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
state: "completed",
|
|
19013
|
-
piMessages: replacement
|
|
18931
|
+
messages: replacement,
|
|
18932
|
+
ttlMs: THREAD_STATE_TTL_MS6
|
|
19014
18933
|
});
|
|
19015
|
-
args.conversation.processing.lastSessionId = nextSessionId;
|
|
19016
18934
|
updateConversationStats(args.conversation);
|
|
19017
18935
|
setSpanAttributes({
|
|
19018
18936
|
"app.compaction.input_messages": sourceMessages.length,
|
|
19019
18937
|
"app.compaction.retained_messages": replacement.length - 1,
|
|
19020
18938
|
"app.compaction.summary_chars": summary.length,
|
|
19021
|
-
"app.compaction.previous_session_id": args.previousSessionId,
|
|
19022
|
-
"app.compaction.next_session_id": nextSessionId,
|
|
19023
18939
|
...context.triggerTokens !== void 0 ? { "app.compaction.trigger_tokens": context.triggerTokens } : {},
|
|
19024
18940
|
"app.context_tokens_estimated": context.estimatedTokens
|
|
19025
18941
|
});
|
|
19026
18942
|
return {
|
|
19027
18943
|
compacted: true,
|
|
19028
|
-
piMessages: replacement
|
|
19029
|
-
sessionId: nextSessionId
|
|
18944
|
+
piMessages: replacement
|
|
19030
18945
|
};
|
|
19031
18946
|
}
|
|
19032
18947
|
function createContextCompactor(deps) {
|
|
@@ -19736,7 +19651,7 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
19736
19651
|
assistantThreadContext.threadTs,
|
|
19737
19652
|
title
|
|
19738
19653
|
);
|
|
19739
|
-
return titleSourceMessage.id;
|
|
19654
|
+
return { sourceMessageId: titleSourceMessage.id, title };
|
|
19740
19655
|
} catch (error) {
|
|
19741
19656
|
const slackErrorCode = getSlackApiErrorCode(error);
|
|
19742
19657
|
const assistantTitleErrorAttributes = {
|
|
@@ -19760,7 +19675,7 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
19760
19675
|
assistantTitleErrorAttributes,
|
|
19761
19676
|
"Skipping thread title update due to Slack permission error"
|
|
19762
19677
|
);
|
|
19763
|
-
return titleSourceMessage.id;
|
|
19678
|
+
return { sourceMessageId: titleSourceMessage.id };
|
|
19764
19679
|
}
|
|
19765
19680
|
logWarn(
|
|
19766
19681
|
"thread_title_generation_failed",
|
|
@@ -19814,6 +19729,27 @@ function collectCanvasUrls(artifacts) {
|
|
|
19814
19729
|
].filter((url) => typeof url === "string" && url !== "")
|
|
19815
19730
|
);
|
|
19816
19731
|
}
|
|
19732
|
+
function turnRequester(args) {
|
|
19733
|
+
const requester = {
|
|
19734
|
+
...args.email ? { email: args.email } : {},
|
|
19735
|
+
...args.fullName ? { fullName: args.fullName } : {},
|
|
19736
|
+
...args.userId ? { slackUserId: args.userId } : {},
|
|
19737
|
+
...args.userName ? { slackUserName: args.userName } : {}
|
|
19738
|
+
};
|
|
19739
|
+
return Object.keys(requester).length > 0 ? requester : void 0;
|
|
19740
|
+
}
|
|
19741
|
+
async function resolveChannelName(thread) {
|
|
19742
|
+
const existingName = thread.channel.name?.trim();
|
|
19743
|
+
if (existingName) {
|
|
19744
|
+
return existingName;
|
|
19745
|
+
}
|
|
19746
|
+
try {
|
|
19747
|
+
const metadata = await thread.channel.fetchMetadata();
|
|
19748
|
+
return metadata.name?.trim() || void 0;
|
|
19749
|
+
} catch {
|
|
19750
|
+
return void 0;
|
|
19751
|
+
}
|
|
19752
|
+
}
|
|
19817
19753
|
function getCurrentTurnCanvasUrl(args) {
|
|
19818
19754
|
const previousUrls = collectCanvasUrls(args.before);
|
|
19819
19755
|
const latestUrls = collectCanvasUrls(args.after);
|
|
@@ -19827,35 +19763,37 @@ function getCurrentTurnCanvasUrl(args) {
|
|
|
19827
19763
|
function buildCanvasRecoveryReply(canvasUrl) {
|
|
19828
19764
|
return `I created the canvas, but the turn was interrupted before I could finish the thread reply: ${canvasUrl}`;
|
|
19829
19765
|
}
|
|
19766
|
+
function collectTurnAttachments(message, queuedMessages) {
|
|
19767
|
+
return [
|
|
19768
|
+
...(queuedMessages ?? []).flatMap((queued) => queued.message.attachments),
|
|
19769
|
+
...message.attachments
|
|
19770
|
+
];
|
|
19771
|
+
}
|
|
19830
19772
|
async function loadPiMessagesForTurn(args) {
|
|
19831
19773
|
const fallback = args.fallback.length > 0 ? [...args.fallback] : void 0;
|
|
19832
19774
|
if (!args.conversationId) {
|
|
19833
19775
|
return { piMessages: fallback };
|
|
19834
19776
|
}
|
|
19835
19777
|
if (args.activeTurnId) {
|
|
19836
|
-
const
|
|
19778
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
19837
19779
|
args.conversationId,
|
|
19838
19780
|
args.activeTurnId
|
|
19839
19781
|
);
|
|
19840
|
-
if (
|
|
19782
|
+
if (sessionRecord?.piMessages.length) {
|
|
19841
19783
|
return {
|
|
19842
19784
|
piMessages: stripRuntimeTurnContext(
|
|
19843
|
-
trimTrailingAssistantMessages(
|
|
19785
|
+
trimTrailingAssistantMessages(sessionRecord.piMessages)
|
|
19844
19786
|
)
|
|
19845
19787
|
};
|
|
19846
19788
|
}
|
|
19847
19789
|
}
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
}
|
|
19851
|
-
|
|
19852
|
-
args.conversationId,
|
|
19853
|
-
args.lastSessionId
|
|
19854
|
-
);
|
|
19855
|
-
if (checkpoint?.state === "completed" && checkpoint.piMessages.length > 0) {
|
|
19790
|
+
const projection = await loadProjection({
|
|
19791
|
+
conversationId: args.conversationId
|
|
19792
|
+
});
|
|
19793
|
+
if (projection.length > 0) {
|
|
19856
19794
|
return {
|
|
19857
|
-
|
|
19858
|
-
piMessages:
|
|
19795
|
+
canCompact: true,
|
|
19796
|
+
piMessages: projection
|
|
19859
19797
|
};
|
|
19860
19798
|
}
|
|
19861
19799
|
return { piMessages: fallback };
|
|
@@ -19867,6 +19805,7 @@ function createReplyToThread(deps) {
|
|
|
19867
19805
|
}
|
|
19868
19806
|
const threadId = getThreadId(thread, message);
|
|
19869
19807
|
const channelId = getChannelId(thread, message);
|
|
19808
|
+
const channelName = channelId ? await resolveChannelName(thread) : void 0;
|
|
19870
19809
|
const threadTs = getThreadTs(threadId);
|
|
19871
19810
|
const assistantThreadContext = getAssistantThreadContext(message);
|
|
19872
19811
|
const messageTs = getMessageTs(message);
|
|
@@ -19889,17 +19828,25 @@ function createReplyToThread(deps) {
|
|
|
19889
19828
|
const strippedUserText = stripLeadingBotMention(message.text, {
|
|
19890
19829
|
stripLeadingSlackMentionToken: options.explicitMention || Boolean(message.isMention)
|
|
19891
19830
|
});
|
|
19892
|
-
const
|
|
19893
|
-
|
|
19894
|
-
|
|
19895
|
-
|
|
19831
|
+
const currentText = {
|
|
19832
|
+
rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
|
|
19833
|
+
userText: appendSlackLegacyAttachmentText(
|
|
19834
|
+
strippedUserText,
|
|
19835
|
+
message.raw
|
|
19836
|
+
)
|
|
19837
|
+
};
|
|
19838
|
+
const effectiveUserText = combineTurnText(
|
|
19839
|
+
options.queuedMessages ?? [],
|
|
19840
|
+
currentText
|
|
19841
|
+
).userText;
|
|
19896
19842
|
const preparedState = options.preparedState ?? await deps.prepareTurnState({
|
|
19897
19843
|
thread,
|
|
19898
19844
|
message,
|
|
19899
|
-
|
|
19845
|
+
text: currentText,
|
|
19900
19846
|
explicitMention: Boolean(
|
|
19901
19847
|
options.explicitMention || message.isMention
|
|
19902
19848
|
),
|
|
19849
|
+
queuedMessages: options.queuedMessages,
|
|
19903
19850
|
context: {
|
|
19904
19851
|
threadId,
|
|
19905
19852
|
requesterId: message.author.userId,
|
|
@@ -19909,6 +19856,15 @@ function createReplyToThread(deps) {
|
|
|
19909
19856
|
});
|
|
19910
19857
|
const slackMessageTs = getSlackMessageTs(message);
|
|
19911
19858
|
const turnId = buildDeterministicTurnId(message.id);
|
|
19859
|
+
const fallbackIdentity = await deps.services.lookupSlackUser(
|
|
19860
|
+
message.author.userId
|
|
19861
|
+
);
|
|
19862
|
+
const requester = turnRequester({
|
|
19863
|
+
email: fallbackIdentity?.email,
|
|
19864
|
+
fullName: message.author.fullName ?? fallbackIdentity?.fullName,
|
|
19865
|
+
userId: message.author.userId,
|
|
19866
|
+
userName: message.author.userName ?? fallbackIdentity?.userName
|
|
19867
|
+
});
|
|
19912
19868
|
const turnTraceContext = {
|
|
19913
19869
|
conversationId,
|
|
19914
19870
|
slackThreadId: threadId,
|
|
@@ -19990,7 +19946,7 @@ function createReplyToThread(deps) {
|
|
|
19990
19946
|
"agent_turn_continuation_retry_schedule_failed",
|
|
19991
19947
|
turnTraceContext,
|
|
19992
19948
|
{
|
|
19993
|
-
"app.ai.
|
|
19949
|
+
"app.ai.resume_session_version": resumeRequest.expectedVersion,
|
|
19994
19950
|
"app.ai.resume_session_id": resumeRequest.sessionId,
|
|
19995
19951
|
...messageTs ? { "messaging.message.id": messageTs } : {}
|
|
19996
19952
|
},
|
|
@@ -20013,11 +19969,10 @@ function createReplyToThread(deps) {
|
|
|
20013
19969
|
return;
|
|
20014
19970
|
}
|
|
20015
19971
|
}
|
|
20016
|
-
const lastSessionIdForHistory = preparedState.conversation.processing.lastSessionId;
|
|
20017
19972
|
const configReply = await maybeApplyProviderDefaultConfigRequest({
|
|
20018
19973
|
channelConfiguration: preparedState.channelConfiguration,
|
|
20019
19974
|
requesterId: message.author.userId,
|
|
20020
|
-
text:
|
|
19975
|
+
text: effectiveUserText
|
|
20021
19976
|
});
|
|
20022
19977
|
if (configReply) {
|
|
20023
19978
|
await beforeFirstResponsePost();
|
|
@@ -20053,6 +20008,28 @@ function createReplyToThread(deps) {
|
|
|
20053
20008
|
nextTurnId: turnId,
|
|
20054
20009
|
updateConversationStats
|
|
20055
20010
|
});
|
|
20011
|
+
if (conversationId) {
|
|
20012
|
+
void recordAgentTurnSessionSummary({
|
|
20013
|
+
channelName,
|
|
20014
|
+
conversationId,
|
|
20015
|
+
sessionId: turnId,
|
|
20016
|
+
sliceId: 1,
|
|
20017
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20018
|
+
state: "running",
|
|
20019
|
+
requester,
|
|
20020
|
+
traceId: getActiveTraceId()
|
|
20021
|
+
}).catch((error) => {
|
|
20022
|
+
logException(
|
|
20023
|
+
error,
|
|
20024
|
+
"agent_turn_summary_record_failed",
|
|
20025
|
+
turnTraceContext,
|
|
20026
|
+
{
|
|
20027
|
+
"app.agent.turn.state": "running"
|
|
20028
|
+
},
|
|
20029
|
+
"Failed to record running turn summary"
|
|
20030
|
+
);
|
|
20031
|
+
});
|
|
20032
|
+
}
|
|
20056
20033
|
setTags({
|
|
20057
20034
|
conversationId
|
|
20058
20035
|
});
|
|
@@ -20070,15 +20047,16 @@ function createReplyToThread(deps) {
|
|
|
20070
20047
|
await persistThreadState(thread, {
|
|
20071
20048
|
conversation: preparedState.conversation
|
|
20072
20049
|
});
|
|
20073
|
-
const fallbackIdentity = await deps.services.lookupSlackUser(
|
|
20074
|
-
message.author.userId
|
|
20075
|
-
);
|
|
20076
20050
|
const resolvedUserName = message.author.userName ?? fallbackIdentity?.userName;
|
|
20077
20051
|
if (resolvedUserName) {
|
|
20078
20052
|
setTags({ slackUserName: resolvedUserName });
|
|
20079
20053
|
}
|
|
20054
|
+
const turnAttachments = collectTurnAttachments(
|
|
20055
|
+
message,
|
|
20056
|
+
options.queuedMessages
|
|
20057
|
+
);
|
|
20080
20058
|
const userAttachments = await deps.resolveUserAttachments(
|
|
20081
|
-
|
|
20059
|
+
turnAttachments,
|
|
20082
20060
|
{
|
|
20083
20061
|
threadId,
|
|
20084
20062
|
requesterId: message.author.userId,
|
|
@@ -20088,7 +20066,7 @@ function createReplyToThread(deps) {
|
|
|
20088
20066
|
messageTs: slackMessageTs
|
|
20089
20067
|
}
|
|
20090
20068
|
);
|
|
20091
|
-
const omittedImageAttachmentCount = !isVisionEnabled() && hasPotentialImageAttachment(
|
|
20069
|
+
const omittedImageAttachmentCount = !isVisionEnabled() && hasPotentialImageAttachment(turnAttachments) ? countPotentialImageAttachments(turnAttachments) : 0;
|
|
20092
20070
|
const status = createSlackAdapterAssistantStatusSession({
|
|
20093
20071
|
channelId: assistantThreadContext?.channelId,
|
|
20094
20072
|
threadTs: assistantThreadContext?.threadTs,
|
|
@@ -20123,11 +20101,10 @@ function createReplyToThread(deps) {
|
|
|
20123
20101
|
const loadedPiMessages = await loadPiMessagesForTurn({
|
|
20124
20102
|
conversationId,
|
|
20125
20103
|
activeTurnId,
|
|
20126
|
-
lastSessionId: lastSessionIdForHistory,
|
|
20127
20104
|
fallback: preparedState.conversation.piMessages
|
|
20128
20105
|
});
|
|
20129
20106
|
let piMessages = loadedPiMessages.piMessages;
|
|
20130
|
-
if (conversationId && loadedPiMessages.
|
|
20107
|
+
if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
|
|
20131
20108
|
const compaction = await deps.services.contextCompactor.maybeCompact({
|
|
20132
20109
|
conversation: preparedState.conversation,
|
|
20133
20110
|
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
@@ -20139,7 +20116,7 @@ function createReplyToThread(deps) {
|
|
|
20139
20116
|
runId
|
|
20140
20117
|
},
|
|
20141
20118
|
onCompactionStart: () => status.start(compactingStatus),
|
|
20142
|
-
|
|
20119
|
+
piMessages
|
|
20143
20120
|
});
|
|
20144
20121
|
if (compaction.compacted) {
|
|
20145
20122
|
piMessages = compaction.piMessages;
|
|
@@ -20163,61 +20140,65 @@ function createReplyToThread(deps) {
|
|
|
20163
20140
|
threadId
|
|
20164
20141
|
});
|
|
20165
20142
|
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,
|
|
20143
|
+
let reply = await deps.services.generateAssistantReply(
|
|
20144
|
+
effectiveUserText,
|
|
20145
|
+
{
|
|
20146
|
+
requester: {
|
|
20147
|
+
userId: message.author.userId,
|
|
20148
|
+
userName: message.author.userName ?? fallbackIdentity?.userName,
|
|
20149
|
+
fullName: message.author.fullName ?? fallbackIdentity?.fullName,
|
|
20150
|
+
email: fallbackIdentity?.email
|
|
20151
|
+
},
|
|
20152
|
+
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
20153
|
+
artifactState: preparedState.artifacts,
|
|
20154
|
+
piMessages,
|
|
20155
|
+
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
20156
|
+
configuration: preparedState.configuration,
|
|
20157
|
+
channelConfiguration: preparedState.channelConfiguration,
|
|
20158
|
+
inboundAttachmentCount: turnAttachments.length,
|
|
20159
|
+
omittedImageAttachmentCount,
|
|
20160
|
+
userAttachments,
|
|
20161
|
+
correlation: {
|
|
20211
20162
|
conversationId,
|
|
20212
|
-
|
|
20213
|
-
|
|
20214
|
-
|
|
20215
|
-
|
|
20216
|
-
|
|
20217
|
-
|
|
20218
|
-
|
|
20219
|
-
|
|
20220
|
-
|
|
20163
|
+
threadId,
|
|
20164
|
+
turnId,
|
|
20165
|
+
threadTs,
|
|
20166
|
+
messageTs,
|
|
20167
|
+
teamId,
|
|
20168
|
+
runId,
|
|
20169
|
+
channelId,
|
|
20170
|
+
channelName,
|
|
20171
|
+
requesterId: message.author.userId
|
|
20172
|
+
},
|
|
20173
|
+
toolChannelId,
|
|
20174
|
+
sandbox: {
|
|
20175
|
+
sandboxId: preparedState.sandboxId,
|
|
20176
|
+
sandboxDependencyProfileHash: preparedState.sandboxDependencyProfileHash
|
|
20177
|
+
},
|
|
20178
|
+
onSandboxAcquired: async (sandbox) => {
|
|
20179
|
+
await persistThreadState(thread, {
|
|
20180
|
+
sandboxId: sandbox.sandboxId,
|
|
20181
|
+
sandboxDependencyProfileHash: sandbox.sandboxDependencyProfileHash
|
|
20182
|
+
});
|
|
20183
|
+
},
|
|
20184
|
+
onArtifactStateUpdated: async (artifacts) => {
|
|
20185
|
+
latestArtifacts = artifacts;
|
|
20186
|
+
await persistThreadState(thread, { artifacts });
|
|
20187
|
+
},
|
|
20188
|
+
onAuthPending: async (pendingAuth) => {
|
|
20189
|
+
await applyPendingAuthUpdate({
|
|
20190
|
+
conversation: preparedState.conversation,
|
|
20191
|
+
conversationId,
|
|
20192
|
+
nextPendingAuth: pendingAuth
|
|
20193
|
+
});
|
|
20194
|
+
await persistThreadState(thread, {
|
|
20195
|
+
conversation: preparedState.conversation
|
|
20196
|
+
});
|
|
20197
|
+
},
|
|
20198
|
+
onStatus: (nextStatus) => status.update(nextStatus),
|
|
20199
|
+
onToolInvocation: options.onToolInvocation
|
|
20200
|
+
}
|
|
20201
|
+
);
|
|
20221
20202
|
const diagnosticsContext = {
|
|
20222
20203
|
slackThreadId: threadId,
|
|
20223
20204
|
slackUserId: message.author.userId,
|
|
@@ -20305,7 +20286,10 @@ function createReplyToThread(deps) {
|
|
|
20305
20286
|
}
|
|
20306
20287
|
const titleUpdateResult = await assistantTitleTask;
|
|
20307
20288
|
if (titleUpdateResult) {
|
|
20308
|
-
artifactStatePatch.assistantTitleSourceMessageId = titleUpdateResult;
|
|
20289
|
+
artifactStatePatch.assistantTitleSourceMessageId = titleUpdateResult.sourceMessageId;
|
|
20290
|
+
if (titleUpdateResult.title) {
|
|
20291
|
+
artifactStatePatch.assistantTitle = titleUpdateResult.title;
|
|
20292
|
+
}
|
|
20309
20293
|
}
|
|
20310
20294
|
const completedState = buildDeliveredTurnStatePatch({
|
|
20311
20295
|
artifactStatePatch,
|
|
@@ -20318,6 +20302,21 @@ function createReplyToThread(deps) {
|
|
|
20318
20302
|
await persistThreadState(thread, {
|
|
20319
20303
|
...completedState
|
|
20320
20304
|
});
|
|
20305
|
+
if (conversationId) {
|
|
20306
|
+
await recordAgentTurnSessionSummary({
|
|
20307
|
+
channelName,
|
|
20308
|
+
conversationId,
|
|
20309
|
+
cumulativeDurationMs: reply.diagnostics.durationMs,
|
|
20310
|
+
cumulativeUsage: reply.diagnostics.usage,
|
|
20311
|
+
sessionId: turnId,
|
|
20312
|
+
sliceId: 1,
|
|
20313
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20314
|
+
state: "completed",
|
|
20315
|
+
conversationTitle: titleUpdateResult?.title,
|
|
20316
|
+
requester,
|
|
20317
|
+
traceId: getActiveTraceId()
|
|
20318
|
+
});
|
|
20319
|
+
}
|
|
20321
20320
|
preparedState.conversation = completedState.conversation;
|
|
20322
20321
|
persistedAtLeastOnce = true;
|
|
20323
20322
|
if (shouldEmitDevAgentTrace()) {
|
|
@@ -20349,14 +20348,14 @@ function createReplyToThread(deps) {
|
|
|
20349
20348
|
if (isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
20350
20349
|
const conversationIdForResume = error.metadata?.conversationId;
|
|
20351
20350
|
const sessionIdForResume = error.metadata?.sessionId;
|
|
20352
|
-
const
|
|
20351
|
+
const version = error.metadata?.version;
|
|
20353
20352
|
const nextSliceId = error.metadata?.sliceId;
|
|
20354
|
-
if (conversationIdForResume && sessionIdForResume && typeof
|
|
20353
|
+
if (conversationIdForResume && sessionIdForResume && typeof version === "number" && canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
20355
20354
|
try {
|
|
20356
20355
|
await deps.services.scheduleTurnTimeoutResume({
|
|
20357
20356
|
conversationId: conversationIdForResume,
|
|
20358
20357
|
sessionId: sessionIdForResume,
|
|
20359
|
-
|
|
20358
|
+
expectedVersion: version
|
|
20360
20359
|
});
|
|
20361
20360
|
shouldPersistFailureState = false;
|
|
20362
20361
|
} catch (scheduleError) {
|
|
@@ -20366,7 +20365,7 @@ function createReplyToThread(deps) {
|
|
|
20366
20365
|
turnTraceContext,
|
|
20367
20366
|
{
|
|
20368
20367
|
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
20369
|
-
"app.ai.
|
|
20368
|
+
"app.ai.resume_session_version": version
|
|
20370
20369
|
},
|
|
20371
20370
|
"Failed to schedule timeout resume callback"
|
|
20372
20371
|
);
|
|
@@ -20375,7 +20374,7 @@ function createReplyToThread(deps) {
|
|
|
20375
20374
|
}
|
|
20376
20375
|
await postTurnContinuationNotice();
|
|
20377
20376
|
return;
|
|
20378
|
-
} else if (conversationIdForResume && sessionIdForResume && typeof
|
|
20377
|
+
} else if (conversationIdForResume && sessionIdForResume && typeof version === "number") {
|
|
20379
20378
|
logWarn(
|
|
20380
20379
|
"agent_turn_timeout_resume_slice_limit_reached",
|
|
20381
20380
|
turnTraceContext,
|
|
@@ -20465,18 +20464,35 @@ function createReplyToThread(deps) {
|
|
|
20465
20464
|
});
|
|
20466
20465
|
if (conversationId) {
|
|
20467
20466
|
try {
|
|
20468
|
-
await
|
|
20467
|
+
await recordAgentTurnSessionSummary({
|
|
20468
|
+
channelName,
|
|
20469
20469
|
conversationId,
|
|
20470
20470
|
sessionId: turnId,
|
|
20471
|
-
|
|
20471
|
+
sliceId: 1,
|
|
20472
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
20473
|
+
state: "failed",
|
|
20474
|
+
requester,
|
|
20475
|
+
traceId: getActiveTraceId()
|
|
20472
20476
|
});
|
|
20473
|
-
|
|
20477
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
20478
|
+
conversationId,
|
|
20479
|
+
turnId
|
|
20480
|
+
);
|
|
20481
|
+
if (sessionRecord) {
|
|
20482
|
+
await failAgentTurnSessionRecord({
|
|
20483
|
+
conversationId,
|
|
20484
|
+
expectedVersion: sessionRecord.version,
|
|
20485
|
+
sessionId: turnId,
|
|
20486
|
+
errorMessage: "Agent turn failed before final reply delivery completed"
|
|
20487
|
+
});
|
|
20488
|
+
}
|
|
20489
|
+
} catch (recordError) {
|
|
20474
20490
|
logException(
|
|
20475
|
-
|
|
20476
|
-
"
|
|
20491
|
+
recordError,
|
|
20492
|
+
"agent_turn_failed_session_record_persist_failed",
|
|
20477
20493
|
turnTraceContext,
|
|
20478
20494
|
{},
|
|
20479
|
-
"Failed to mark failed turn
|
|
20495
|
+
"Failed to mark failed turn session record"
|
|
20480
20496
|
);
|
|
20481
20497
|
}
|
|
20482
20498
|
}
|
|
@@ -20533,35 +20549,51 @@ async function refreshAssistantThreadContext(event) {
|
|
|
20533
20549
|
await syncAssistantThreadContext(event, { setInitialTitle: false });
|
|
20534
20550
|
}
|
|
20535
20551
|
|
|
20536
|
-
// src/chat/runtime/
|
|
20537
|
-
var
|
|
20538
|
-
function
|
|
20539
|
-
|
|
20540
|
-
|
|
20552
|
+
// src/chat/runtime/conversation-message.ts
|
|
20553
|
+
var NON_TEXT_MESSAGE_TEXT = "[non-text message]";
|
|
20554
|
+
function resolveMessageText(args) {
|
|
20555
|
+
const text = normalizeConversationText(args.text);
|
|
20556
|
+
return text || NON_TEXT_MESSAGE_TEXT;
|
|
20557
|
+
}
|
|
20558
|
+
function toConversationMessage(args) {
|
|
20559
|
+
const messageHasPotentialImageAttachment = hasPotentialImageAttachment(
|
|
20560
|
+
args.entry.attachments
|
|
20541
20561
|
);
|
|
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
|
-
}
|
|
20562
|
+
const imageAttachmentCount = messageHasPotentialImageAttachment ? countPotentialImageAttachments(args.entry.attachments) : 0;
|
|
20549
20563
|
return {
|
|
20550
|
-
id: entry.id,
|
|
20551
|
-
role: entry.author.isMe ? "assistant" : "user",
|
|
20552
|
-
text:
|
|
20553
|
-
createdAtMs: entry.metadata.dateSent.getTime(),
|
|
20564
|
+
id: args.entry.id,
|
|
20565
|
+
role: args.entry.author.isMe ? "assistant" : "user",
|
|
20566
|
+
text: resolveMessageText(args),
|
|
20567
|
+
createdAtMs: args.entry.metadata.dateSent.getTime(),
|
|
20554
20568
|
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
|
|
20569
|
+
userId: args.entry.author.userId,
|
|
20570
|
+
userName: args.entry.author.userName,
|
|
20571
|
+
fullName: args.entry.author.fullName,
|
|
20572
|
+
isBot: typeof args.entry.author.isBot === "boolean" ? args.entry.author.isBot : void 0
|
|
20559
20573
|
},
|
|
20560
20574
|
meta: {
|
|
20561
|
-
|
|
20575
|
+
attachmentCount: args.entry.attachments.length,
|
|
20576
|
+
explicitMention: args.explicitMention,
|
|
20577
|
+
imageAttachmentCount: imageAttachmentCount > 0 ? imageAttachmentCount : void 0,
|
|
20578
|
+
imagesHydrated: !messageHasPotentialImageAttachment,
|
|
20579
|
+
slackTs: getSlackMessageTs(args.entry)
|
|
20562
20580
|
}
|
|
20563
20581
|
};
|
|
20564
20582
|
}
|
|
20583
|
+
|
|
20584
|
+
// src/chat/runtime/turn-preparation.ts
|
|
20585
|
+
var BACKFILL_MESSAGE_LIMIT = 80;
|
|
20586
|
+
function hasPendingImageHydration(conversation) {
|
|
20587
|
+
return conversation.messages.some(
|
|
20588
|
+
(message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
|
|
20589
|
+
);
|
|
20590
|
+
}
|
|
20591
|
+
function getBackfillText(entry) {
|
|
20592
|
+
const text = normalizeConversationText(
|
|
20593
|
+
appendSlackLegacyAttachmentText(entry.text, entry.raw)
|
|
20594
|
+
);
|
|
20595
|
+
return text || void 0;
|
|
20596
|
+
}
|
|
20565
20597
|
async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
20566
20598
|
if (conversation.backfill.completedAtMs) {
|
|
20567
20599
|
return;
|
|
@@ -20586,9 +20618,9 @@ async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
|
20586
20618
|
}
|
|
20587
20619
|
fetchedNewestFirst.reverse();
|
|
20588
20620
|
for (const entry of fetchedNewestFirst) {
|
|
20589
|
-
const
|
|
20590
|
-
if (
|
|
20591
|
-
seeded.push(
|
|
20621
|
+
const text = getBackfillText(entry);
|
|
20622
|
+
if (text) {
|
|
20623
|
+
seeded.push(toConversationMessage({ entry, text }));
|
|
20592
20624
|
}
|
|
20593
20625
|
}
|
|
20594
20626
|
if (seeded.length > 0) {
|
|
@@ -20603,9 +20635,9 @@ async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
|
20603
20635
|
}
|
|
20604
20636
|
const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
|
|
20605
20637
|
for (const entry of fromRecent) {
|
|
20606
|
-
const
|
|
20607
|
-
if (
|
|
20608
|
-
seeded.push(
|
|
20638
|
+
const text = getBackfillText(entry);
|
|
20639
|
+
if (text) {
|
|
20640
|
+
seeded.push(toConversationMessage({ entry, text }));
|
|
20609
20641
|
}
|
|
20610
20642
|
}
|
|
20611
20643
|
source = "recent_messages";
|
|
@@ -20642,35 +20674,26 @@ function createPrepareTurnState(deps) {
|
|
|
20642
20674
|
messageId: args.message.id,
|
|
20643
20675
|
messageCreatedAtMs: args.message.metadata.dateSent.getTime()
|
|
20644
20676
|
});
|
|
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
|
-
};
|
|
20677
|
+
for (const queued of args.queuedMessages ?? []) {
|
|
20678
|
+
const queuedMessage = toConversationMessage({
|
|
20679
|
+
entry: queued.message,
|
|
20680
|
+
explicitMention: queued.explicitMention,
|
|
20681
|
+
text: queued.userText
|
|
20682
|
+
});
|
|
20683
|
+
upsertConversationMessage(conversation, queuedMessage);
|
|
20684
|
+
}
|
|
20685
|
+
const incomingUserMessage = toConversationMessage({
|
|
20686
|
+
entry: args.message,
|
|
20687
|
+
explicitMention: args.explicitMention,
|
|
20688
|
+
text: args.text.userText
|
|
20689
|
+
});
|
|
20670
20690
|
const userMessageId = upsertConversationMessage(
|
|
20671
20691
|
conversation,
|
|
20672
20692
|
incomingUserMessage
|
|
20673
20693
|
);
|
|
20694
|
+
const messageHasPotentialImageAttachment = hasPotentialImageAttachment(args.message.attachments) || (args.queuedMessages ?? []).some(
|
|
20695
|
+
(queued) => hasPotentialImageAttachment(queued.message.attachments)
|
|
20696
|
+
);
|
|
20674
20697
|
const shouldHydrateVisionContext = !conversation.vision.backfillCompletedAtMs || messageHasPotentialImageAttachment || hasPendingImageHydration(conversation);
|
|
20675
20698
|
if (isVisionEnabled() && shouldHydrateVisionContext) {
|
|
20676
20699
|
await deps.hydrateConversationVisionContext(conversation, {
|
|
@@ -20757,28 +20780,20 @@ function createSlackRuntime(options) {
|
|
|
20757
20780
|
message,
|
|
20758
20781
|
decision,
|
|
20759
20782
|
completedAtMs,
|
|
20760
|
-
|
|
20783
|
+
text
|
|
20761
20784
|
}) => {
|
|
20762
20785
|
const conversation = coerceThreadConversationState(await thread.state);
|
|
20763
|
-
const
|
|
20764
|
-
|
|
20786
|
+
const conversationMessage = toConversationMessage({
|
|
20787
|
+
entry: message,
|
|
20788
|
+
explicitMention: Boolean(message.isMention),
|
|
20789
|
+
text: text.userText
|
|
20790
|
+
});
|
|
20765
20791
|
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
|
-
},
|
|
20792
|
+
...conversationMessage,
|
|
20776
20793
|
meta: {
|
|
20777
|
-
|
|
20778
|
-
slackTs,
|
|
20794
|
+
...conversationMessage.meta,
|
|
20779
20795
|
replied: false,
|
|
20780
|
-
skippedReason: decision.reason
|
|
20781
|
-
imagesHydrated: !hasPotentialImageAttachment(message.attachments)
|
|
20796
|
+
skippedReason: decision.reason
|
|
20782
20797
|
}
|
|
20783
20798
|
});
|
|
20784
20799
|
conversation.processing.activeTurnId = void 0;
|
|
@@ -21221,17 +21236,26 @@ function createProductionBot() {
|
|
|
21221
21236
|
});
|
|
21222
21237
|
}
|
|
21223
21238
|
function registerProductionHandlers(bot, slackRuntime) {
|
|
21224
|
-
bot.onNewMention((thread, message) => {
|
|
21239
|
+
bot.onNewMention((thread, message, context) => {
|
|
21225
21240
|
rehydrateAttachmentFetchers(message);
|
|
21226
|
-
|
|
21241
|
+
context?.skipped.forEach((skipped) => rehydrateAttachmentFetchers(skipped));
|
|
21242
|
+
return slackRuntime.handleNewMention(thread, message, {
|
|
21243
|
+
messageContext: context
|
|
21244
|
+
});
|
|
21227
21245
|
});
|
|
21228
|
-
bot.onDirectMessage((thread, message) => {
|
|
21246
|
+
bot.onDirectMessage((thread, message, _channel, context) => {
|
|
21229
21247
|
rehydrateAttachmentFetchers(message);
|
|
21230
|
-
|
|
21248
|
+
context?.skipped.forEach((skipped) => rehydrateAttachmentFetchers(skipped));
|
|
21249
|
+
return slackRuntime.handleNewMention(thread, message, {
|
|
21250
|
+
messageContext: context
|
|
21251
|
+
});
|
|
21231
21252
|
});
|
|
21232
|
-
bot.onSubscribedMessage((thread, message) => {
|
|
21253
|
+
bot.onSubscribedMessage((thread, message, context) => {
|
|
21233
21254
|
rehydrateAttachmentFetchers(message);
|
|
21234
|
-
|
|
21255
|
+
context?.skipped.forEach((skipped) => rehydrateAttachmentFetchers(skipped));
|
|
21256
|
+
return slackRuntime.handleSubscribedMessage(thread, message, {
|
|
21257
|
+
messageContext: context
|
|
21258
|
+
});
|
|
21235
21259
|
});
|
|
21236
21260
|
bot.onAssistantThreadStarted(
|
|
21237
21261
|
(event) => slackRuntime.handleAssistantThreadStarted(event)
|
|
@@ -21718,14 +21742,13 @@ async function createApp(options) {
|
|
|
21718
21742
|
}
|
|
21719
21743
|
await next();
|
|
21720
21744
|
});
|
|
21721
|
-
app.get("/", () =>
|
|
21722
|
-
app.get("/health", () =>
|
|
21723
|
-
app.get("/api/info", () => GET());
|
|
21745
|
+
app.get("/", () => GET());
|
|
21746
|
+
app.get("/health", () => GET());
|
|
21724
21747
|
app.get("/api/oauth/callback/mcp/:provider", (c) => {
|
|
21725
|
-
return
|
|
21748
|
+
return GET3(c.req.raw, c.req.param("provider"), waitUntil);
|
|
21726
21749
|
});
|
|
21727
21750
|
app.get("/api/oauth/callback/:provider", (c) => {
|
|
21728
|
-
return
|
|
21751
|
+
return GET4(c.req.raw, c.req.param("provider"), waitUntil);
|
|
21729
21752
|
});
|
|
21730
21753
|
app.post("/api/internal/turn-resume", (c) => {
|
|
21731
21754
|
return POST2(c.req.raw, waitUntil);
|
|
@@ -21734,7 +21757,7 @@ async function createApp(options) {
|
|
|
21734
21757
|
return POST(c.req.raw, waitUntil);
|
|
21735
21758
|
});
|
|
21736
21759
|
app.get("/api/internal/heartbeat", (c) => {
|
|
21737
|
-
return
|
|
21760
|
+
return GET2(c.req.raw, waitUntil);
|
|
21738
21761
|
});
|
|
21739
21762
|
app.post("/api/webhooks/:platform", (c) => {
|
|
21740
21763
|
return POST3(c.req.raw, c.req.param("platform"), waitUntil);
|