@sentry/junior 0.85.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-AV7CHLUO.js → agent-hooks-NWOUB3UR.js} +7 -7
- package/dist/app.js +788 -333
- package/dist/chat/conversation-privacy.d.ts +12 -2
- package/dist/chat/conversations/sql/migrations.d.ts +1 -1
- package/dist/chat/conversations/sql/store.d.ts +9 -1
- package/dist/chat/conversations/store.d.ts +13 -0
- package/dist/chat/ingress/message-changed.d.ts +2 -0
- package/dist/chat/respond.d.ts +7 -1
- package/dist/chat/runtime/reply-executor.d.ts +2 -10
- package/dist/chat/runtime/slack-runtime.d.ts +5 -4
- package/dist/chat/runtime/turn.d.ts +7 -0
- package/dist/chat/services/persist-retry.d.ts +2 -0
- package/dist/chat/services/turn-session-record.d.ts +31 -2
- package/dist/chat/slack/client.d.ts +33 -2
- package/dist/chat/slack/conversation-context.d.ts +15 -0
- package/dist/chat/state/turn-session.d.ts +9 -0
- package/dist/chat/task-execution/queue.d.ts +1 -1
- package/dist/chat/task-execution/slack-work.d.ts +10 -2
- package/dist/chat/task-execution/state.d.ts +44 -4
- package/dist/chat/task-execution/store.d.ts +21 -4
- package/dist/chat/task-execution/worker.d.ts +11 -4
- package/dist/chat/tools/slack/channel-access.d.ts +29 -0
- package/dist/chat/tools/slack/thread-read.d.ts +4 -1
- package/dist/{chunk-UVNV67EC.js → chunk-2NFV5FMB.js} +4 -4
- package/dist/{chunk-GY7L6VCT.js → chunk-37B2R2QJ.js} +15 -10
- package/dist/{chunk-QTTTRBNB.js → chunk-6I6HBOQM.js} +80 -15
- package/dist/{chunk-ENEWQMRJ.js → chunk-6O5UI3RG.js} +1 -1
- package/dist/{chunk-MTE5NRPJ.js → chunk-BRSQQRG6.js} +1 -1
- package/dist/{chunk-EENOFKDN.js → chunk-ENPSU7L7.js} +165 -27
- package/dist/{chunk-MHEV3T2Y.js → chunk-FPHA6GCQ.js} +876 -719
- package/dist/{chunk-RUB77TGS.js → chunk-GB5DFM4D.js} +1 -1
- package/dist/{chunk-C2PSUWK3.js → chunk-GGD6WK6V.js} +124 -25
- package/dist/{chunk-SSUEWAVS.js → chunk-JRXCSSSU.js} +19 -4
- package/dist/{chunk-DR75T7J3.js → chunk-L7OHKDOX.js} +9 -5
- package/dist/{chunk-FYPO4K7C.js → chunk-RIB3M6YA.js} +2 -2
- package/dist/{chunk-BU2AWPEN.js → chunk-ZU2ALUVQ.js} +3 -2
- package/dist/cli/chat.js +4 -4
- package/dist/cli/plugins.js +7 -7
- package/dist/cli/snapshot-warmup.js +4 -4
- package/dist/cli/upgrade.js +10 -7
- package/dist/{db-S3HYBLUS.js → db-SZVUU7RB.js} +4 -4
- package/dist/instrumentation.js +1 -1
- package/dist/nitro.js +4 -4
- package/dist/reporting/conversations.d.ts +2 -1
- package/dist/reporting.js +95 -30
- package/dist/{runner-IMQH6V2B.js → runner-HEBRPNN2.js} +27 -13
- package/package.json +6 -6
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isSnapshotMissingError,
|
|
11
11
|
resolveRuntimeDependencySnapshot,
|
|
12
12
|
runNonInteractiveCommand
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RIB3M6YA.js";
|
|
14
14
|
import {
|
|
15
15
|
pluginTaskId,
|
|
16
16
|
pluginTaskParamsSchema,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
buildSystemPrompt,
|
|
24
24
|
buildTurnContextPrompt,
|
|
25
25
|
getAgentTurnSessionRecord,
|
|
26
|
+
getInterruptionMarker,
|
|
26
27
|
loadConnectedMcpProviders,
|
|
27
28
|
recordAuthorizationRequested,
|
|
28
29
|
recordMcpProviderConnected,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
recordSubagentStarted,
|
|
31
32
|
recordToolExecutionStarted,
|
|
32
33
|
upsertAgentTurnSessionRecord
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-37B2R2QJ.js";
|
|
34
35
|
import {
|
|
35
36
|
createPluginEmbedder,
|
|
36
37
|
createPluginHookRunner,
|
|
@@ -41,14 +42,15 @@ import {
|
|
|
41
42
|
getPlugins,
|
|
42
43
|
getSlackToolContext,
|
|
43
44
|
resolveChannelCapabilities
|
|
44
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-2NFV5FMB.js";
|
|
45
46
|
import {
|
|
46
47
|
createPluginLogger,
|
|
47
48
|
createPluginState
|
|
48
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-BRSQQRG6.js";
|
|
49
50
|
import {
|
|
51
|
+
getConversationStore,
|
|
50
52
|
getDb
|
|
51
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-6I6HBOQM.js";
|
|
52
54
|
import {
|
|
53
55
|
SANDBOX_DATA_ROOT,
|
|
54
56
|
SANDBOX_SKILLS_ROOT,
|
|
@@ -59,7 +61,7 @@ import {
|
|
|
59
61
|
import {
|
|
60
62
|
acquireActiveLock,
|
|
61
63
|
getStateAdapter
|
|
62
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-GB5DFM4D.js";
|
|
63
65
|
import {
|
|
64
66
|
SlackActionError,
|
|
65
67
|
downloadPrivateSlackFile,
|
|
@@ -70,7 +72,7 @@ import {
|
|
|
70
72
|
normalizeSlackConversationId,
|
|
71
73
|
parseDestination,
|
|
72
74
|
withSlackRetries
|
|
73
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-GGD6WK6V.js";
|
|
74
76
|
import {
|
|
75
77
|
GEN_AI_PROVIDER_NAME,
|
|
76
78
|
GEN_AI_SERVER_ADDRESS,
|
|
@@ -106,7 +108,7 @@ import {
|
|
|
106
108
|
toObservablePromptPart,
|
|
107
109
|
trimTrailingAssistantMessages,
|
|
108
110
|
upsertActiveSkill
|
|
109
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-ZU2ALUVQ.js";
|
|
110
112
|
import {
|
|
111
113
|
parseSlackThreadId,
|
|
112
114
|
resolveConversationPrivacy,
|
|
@@ -116,7 +118,7 @@ import {
|
|
|
116
118
|
toGenAiPayloadMetadata,
|
|
117
119
|
toGenAiPayloadTraceAttributes,
|
|
118
120
|
toGenAiTextMetadata
|
|
119
|
-
} from "./chunk-
|
|
121
|
+
} from "./chunk-L7OHKDOX.js";
|
|
120
122
|
import {
|
|
121
123
|
discoverSkills,
|
|
122
124
|
findSkillByName,
|
|
@@ -138,6 +140,7 @@ import {
|
|
|
138
140
|
listReferenceFiles
|
|
139
141
|
} from "./chunk-6APU57E6.js";
|
|
140
142
|
import {
|
|
143
|
+
buildTurnFailureResponse,
|
|
141
144
|
extractGenAiUsageAttributes,
|
|
142
145
|
extractGenAiUsageSummary,
|
|
143
146
|
getActiveTraceId,
|
|
@@ -650,6 +653,16 @@ var TurnInputCommitLostError = class extends Error {
|
|
|
650
653
|
function isTurnInputCommitLostError(error) {
|
|
651
654
|
return error instanceof TurnInputCommitLostError;
|
|
652
655
|
}
|
|
656
|
+
var TurnInputDeferredError = class extends Error {
|
|
657
|
+
code = "turn_input_deferred";
|
|
658
|
+
constructor(message = "Turn input is deferred until the active resume ends") {
|
|
659
|
+
super(message);
|
|
660
|
+
this.name = "TurnInputDeferredError";
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
function isTurnInputDeferredError(error) {
|
|
664
|
+
return error instanceof TurnInputDeferredError;
|
|
665
|
+
}
|
|
653
666
|
function startActiveTurn(args) {
|
|
654
667
|
args.conversation.processing.activeTurnId = args.nextTurnId;
|
|
655
668
|
args.updateConversationStats(args.conversation);
|
|
@@ -679,186 +692,593 @@ function markTurnFailed(args) {
|
|
|
679
692
|
args.updateConversationStats(args.conversation);
|
|
680
693
|
}
|
|
681
694
|
|
|
682
|
-
// src/chat/
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
function
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
695
|
+
// src/chat/usage.ts
|
|
696
|
+
var COMPONENT_USAGE_FIELDS = [
|
|
697
|
+
"inputTokens",
|
|
698
|
+
"outputTokens",
|
|
699
|
+
"cachedInputTokens",
|
|
700
|
+
"cacheCreationTokens"
|
|
701
|
+
];
|
|
702
|
+
function hasAgentTurnUsage(usage) {
|
|
703
|
+
return Boolean(
|
|
704
|
+
usage && Object.values(usage).some(
|
|
705
|
+
(value) => typeof value === "number" && Number.isFinite(value)
|
|
706
|
+
)
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
function getFiniteCount(value) {
|
|
710
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
711
|
+
}
|
|
712
|
+
function getComponentTotal(usage) {
|
|
713
|
+
let total;
|
|
714
|
+
for (const field of COMPONENT_USAGE_FIELDS) {
|
|
715
|
+
const value = getFiniteCount(usage[field]);
|
|
716
|
+
if (value === void 0) continue;
|
|
717
|
+
total = (total ?? 0) + value;
|
|
718
|
+
}
|
|
719
|
+
return total;
|
|
720
|
+
}
|
|
721
|
+
function addAgentTurnUsage(...usages) {
|
|
722
|
+
const components = {};
|
|
723
|
+
let componentTotal;
|
|
724
|
+
let totalOnlyTokens;
|
|
725
|
+
for (const usage of usages) {
|
|
726
|
+
if (!usage) continue;
|
|
727
|
+
const usageComponentTotal = getComponentTotal(usage);
|
|
728
|
+
if (usageComponentTotal !== void 0) {
|
|
729
|
+
componentTotal = (componentTotal ?? 0) + usageComponentTotal;
|
|
730
|
+
for (const field of COMPONENT_USAGE_FIELDS) {
|
|
731
|
+
const value = getFiniteCount(usage[field]);
|
|
732
|
+
if (value === void 0) continue;
|
|
733
|
+
components[field] = (components[field] ?? 0) + value;
|
|
700
734
|
}
|
|
701
|
-
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const totalTokens = getFiniteCount(usage.totalTokens);
|
|
738
|
+
if (totalTokens !== void 0) {
|
|
739
|
+
totalOnlyTokens = (totalOnlyTokens ?? 0) + totalTokens;
|
|
702
740
|
}
|
|
703
741
|
}
|
|
704
|
-
|
|
705
|
-
|
|
742
|
+
if (totalOnlyTokens !== void 0) {
|
|
743
|
+
return {
|
|
744
|
+
totalTokens: totalOnlyTokens + (componentTotal ?? 0)
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
return hasAgentTurnUsage(components) ? components : void 0;
|
|
706
748
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
...new Set(providers.flatMap((p) => p.configKeys))
|
|
715
|
-
].sort();
|
|
716
|
-
logInfo(
|
|
717
|
-
"capability_catalog_loaded",
|
|
718
|
-
{},
|
|
749
|
+
|
|
750
|
+
// src/chat/services/turn-session-record.ts
|
|
751
|
+
var AGENT_CONTINUE_MAX_SLICES = 48;
|
|
752
|
+
function logSessionRecordError(error, eventName, args, attributes, message) {
|
|
753
|
+
logException(
|
|
754
|
+
error,
|
|
755
|
+
eventName,
|
|
719
756
|
{
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
757
|
+
slackThreadId: args.logContext.threadId,
|
|
758
|
+
slackUserId: args.logContext.requesterId,
|
|
759
|
+
slackChannelId: args.logContext.channelId,
|
|
760
|
+
runId: args.logContext.runId,
|
|
761
|
+
assistantUserName: args.logContext.assistantUserName,
|
|
762
|
+
modelId: args.logContext.modelId
|
|
725
763
|
},
|
|
726
|
-
|
|
764
|
+
{
|
|
765
|
+
"app.ai.resume_conversation_id": args.conversationId,
|
|
766
|
+
"app.ai.resume_session_id": args.sessionId,
|
|
767
|
+
...attributes
|
|
768
|
+
},
|
|
769
|
+
message
|
|
727
770
|
);
|
|
728
771
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
772
|
+
function addDurationMs(prior, current) {
|
|
773
|
+
const total = [prior, current].reduce((sum, value) => {
|
|
774
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
775
|
+
return sum;
|
|
776
|
+
}
|
|
777
|
+
return (sum ?? 0) + Math.max(0, Math.floor(value));
|
|
778
|
+
}, void 0);
|
|
779
|
+
return total;
|
|
780
|
+
}
|
|
781
|
+
function isContinuableBoundary(messages) {
|
|
782
|
+
const lastRole = getPiMessageRole(messages.at(-1));
|
|
783
|
+
return lastRole === "user" || lastRole === "toolResult";
|
|
784
|
+
}
|
|
785
|
+
function resumableBoundary(messages, fallbackMessages) {
|
|
786
|
+
const current = trimTrailingAssistantMessages(messages);
|
|
787
|
+
if (current.length > 0 && isContinuableBoundary(current)) {
|
|
788
|
+
return current;
|
|
735
789
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
790
|
+
return trimTrailingAssistantMessages(fallbackMessages ?? []);
|
|
791
|
+
}
|
|
792
|
+
async function loadTurnSessionRecord(ctx) {
|
|
793
|
+
const canUseTurnSession = Boolean(ctx.conversationId && ctx.sessionId);
|
|
794
|
+
const existingSessionRecord = canUseTurnSession && ctx.conversationId && ctx.sessionId ? await getAgentTurnSessionRecord(ctx.conversationId, ctx.sessionId) : void 0;
|
|
795
|
+
const hasAwaitingResumeRecord = Boolean(
|
|
796
|
+
existingSessionRecord && existingSessionRecord.state === "awaiting_resume"
|
|
797
|
+
);
|
|
798
|
+
return {
|
|
799
|
+
canUseTurnSession,
|
|
800
|
+
resumedFromSessionRecord: hasAwaitingResumeRecord,
|
|
801
|
+
currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
|
|
802
|
+
existingSessionRecord
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
async function persistRunningSessionRecord(args) {
|
|
806
|
+
if (args.messages.length === 0 || !isContinuableBoundary(args.messages)) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
811
|
+
args.conversationId,
|
|
812
|
+
args.sessionId
|
|
813
|
+
);
|
|
814
|
+
await upsertAgentTurnSessionRecord({
|
|
815
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
816
|
+
conversationId: args.conversationId,
|
|
817
|
+
cumulativeDurationMs: latestSessionRecord?.cumulativeDurationMs,
|
|
818
|
+
cumulativeUsage: latestSessionRecord?.cumulativeUsage,
|
|
819
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
820
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
821
|
+
sessionId: args.sessionId,
|
|
822
|
+
sliceId: args.sliceId,
|
|
823
|
+
state: "running",
|
|
824
|
+
piMessages: args.messages,
|
|
825
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
826
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
827
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
828
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
829
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
830
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
831
|
+
} : {}
|
|
832
|
+
});
|
|
833
|
+
return true;
|
|
834
|
+
} catch (recordError) {
|
|
835
|
+
logSessionRecordError(
|
|
836
|
+
recordError,
|
|
837
|
+
"agent_turn_running_session_record_failed",
|
|
838
|
+
args,
|
|
839
|
+
{
|
|
840
|
+
"app.ai.resume_slice_id": args.sliceId
|
|
841
|
+
},
|
|
842
|
+
"Failed to persist running turn session record"
|
|
843
|
+
);
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
async function persistCompletedSessionRecord(args) {
|
|
848
|
+
let sliceId = args.sliceId;
|
|
849
|
+
try {
|
|
850
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
851
|
+
args.conversationId,
|
|
852
|
+
args.sessionId
|
|
853
|
+
);
|
|
854
|
+
sliceId = sliceId ?? latestSessionRecord?.sliceId;
|
|
855
|
+
if (sliceId === void 0) {
|
|
739
856
|
throw new Error(
|
|
740
|
-
|
|
857
|
+
"Completed session record requires a slice id from the caller or the latest stored record"
|
|
741
858
|
);
|
|
742
859
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
860
|
+
await upsertAgentTurnSessionRecord({
|
|
861
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
862
|
+
conversationId: args.conversationId,
|
|
863
|
+
cumulativeDurationMs: addDurationMs(
|
|
864
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
865
|
+
args.currentDurationMs
|
|
866
|
+
),
|
|
867
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
868
|
+
latestSessionRecord?.cumulativeUsage,
|
|
869
|
+
args.currentUsage
|
|
870
|
+
),
|
|
871
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
872
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
873
|
+
...args.destinationVisibility ? { destinationVisibility: args.destinationVisibility } : {},
|
|
874
|
+
sessionId: args.sessionId,
|
|
875
|
+
sliceId,
|
|
876
|
+
state: "completed",
|
|
877
|
+
piMessages: args.allMessages,
|
|
878
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
879
|
+
...args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames ? {
|
|
880
|
+
loadedSkillNames: args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames
|
|
881
|
+
} : {},
|
|
882
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
883
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
884
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
885
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
886
|
+
} : {}
|
|
746
887
|
});
|
|
888
|
+
} catch (recordError) {
|
|
889
|
+
logSessionRecordError(
|
|
890
|
+
recordError,
|
|
891
|
+
"agent_turn_completed_session_record_failed",
|
|
892
|
+
args,
|
|
893
|
+
sliceId !== void 0 ? { "app.ai.resume_slice_id": sliceId } : {},
|
|
894
|
+
"Failed to persist completed turn session record"
|
|
895
|
+
);
|
|
747
896
|
}
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
// src/chat/credentials/user-token-store.ts
|
|
751
|
-
import {
|
|
752
|
-
pluginStoredTokensSchema
|
|
753
|
-
} from "@sentry/junior-plugin-api";
|
|
754
|
-
var storedTokensSchema = pluginStoredTokensSchema;
|
|
755
|
-
|
|
756
|
-
// src/chat/credentials/state-adapter-token-store.ts
|
|
757
|
-
var KEY_PREFIX = "oauth-token";
|
|
758
|
-
var BUFFER_MS = 24 * 60 * 60 * 1e3;
|
|
759
|
-
var LONG_LIVED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
760
|
-
var REFRESH_LOCK_WAIT_MS = 3e4;
|
|
761
|
-
var REFRESH_LOCK_RETRY_MS = 100;
|
|
762
|
-
function tokenKey(userId, provider) {
|
|
763
|
-
return `${KEY_PREFIX}:${userId}:${provider}`;
|
|
764
897
|
}
|
|
765
|
-
function
|
|
766
|
-
|
|
898
|
+
async function completeDeliveredTurn(args) {
|
|
899
|
+
await persistCompletedSessionRecord({
|
|
900
|
+
channelName: args.channelName,
|
|
901
|
+
conversationId: args.conversationId,
|
|
902
|
+
currentDurationMs: args.durationMs,
|
|
903
|
+
currentUsage: args.usage,
|
|
904
|
+
destination: args.destination,
|
|
905
|
+
destinationVisibility: args.destinationVisibility,
|
|
906
|
+
source: args.source,
|
|
907
|
+
sessionId: args.sessionId,
|
|
908
|
+
sliceId: args.sliceId,
|
|
909
|
+
allMessages: args.messages,
|
|
910
|
+
loadedSkillNames: args.loadedSkillNames,
|
|
911
|
+
logContext: args.logContext,
|
|
912
|
+
requester: args.requester,
|
|
913
|
+
surface: args.surface,
|
|
914
|
+
turnStartMessageIndex: args.turnStartMessageIndex
|
|
915
|
+
});
|
|
767
916
|
}
|
|
768
|
-
async function
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const parsed = storedTokensSchema.parse(tokens);
|
|
782
|
-
const expiresAt = parsed.refreshTokenExpiresAt ?? parsed.expiresAt;
|
|
783
|
-
const ttlMs2 = expiresAt ? Math.max(expiresAt - Date.now() + BUFFER_MS, BUFFER_MS) : LONG_LIVED_TTL_MS;
|
|
784
|
-
await this.state.set(tokenKey(userId, provider), parsed, ttlMs2);
|
|
785
|
-
}
|
|
786
|
-
async delete(userId, provider) {
|
|
787
|
-
await this.state.delete(tokenKey(userId, provider));
|
|
788
|
-
}
|
|
789
|
-
/** Wait for the per-slot refresh gate so rotated refresh tokens are used once. */
|
|
790
|
-
async withRefresh(userId, provider, callback) {
|
|
791
|
-
const lockKey = refreshLockKey(userId, provider);
|
|
792
|
-
const deadline = Date.now() + REFRESH_LOCK_WAIT_MS;
|
|
793
|
-
while (true) {
|
|
794
|
-
const lock = await acquireActiveLock(this.state, lockKey);
|
|
795
|
-
if (lock) {
|
|
796
|
-
try {
|
|
797
|
-
return await callback();
|
|
798
|
-
} finally {
|
|
799
|
-
await this.state.releaseLock(lock);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
if (Date.now() >= deadline) {
|
|
803
|
-
throw new Error(`Could not acquire OAuth token refresh lock`);
|
|
804
|
-
}
|
|
805
|
-
await sleep(REFRESH_LOCK_RETRY_MS);
|
|
917
|
+
async function persistAuthPauseSessionRecord(args) {
|
|
918
|
+
const nextSliceId = args.currentSliceId + 1;
|
|
919
|
+
try {
|
|
920
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
921
|
+
args.conversationId,
|
|
922
|
+
args.sessionId
|
|
923
|
+
);
|
|
924
|
+
const piMessages = resumableBoundary(
|
|
925
|
+
args.messages,
|
|
926
|
+
latestSessionRecord?.piMessages
|
|
927
|
+
);
|
|
928
|
+
if (piMessages.length > 0 && !isContinuableBoundary(piMessages)) {
|
|
929
|
+
return void 0;
|
|
806
930
|
}
|
|
931
|
+
return await upsertAgentTurnSessionRecord({
|
|
932
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
933
|
+
conversationId: args.conversationId,
|
|
934
|
+
cumulativeDurationMs: addDurationMs(
|
|
935
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
936
|
+
args.currentDurationMs
|
|
937
|
+
),
|
|
938
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
939
|
+
latestSessionRecord?.cumulativeUsage,
|
|
940
|
+
args.currentUsage
|
|
941
|
+
),
|
|
942
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
943
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
944
|
+
sessionId: args.sessionId,
|
|
945
|
+
sliceId: nextSliceId,
|
|
946
|
+
state: "awaiting_resume",
|
|
947
|
+
piMessages,
|
|
948
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
949
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
950
|
+
resumeReason: "auth",
|
|
951
|
+
resumedFromSliceId: args.currentSliceId,
|
|
952
|
+
errorMessage: args.errorMessage,
|
|
953
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
954
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
955
|
+
});
|
|
956
|
+
} catch (recordError) {
|
|
957
|
+
logSessionRecordError(
|
|
958
|
+
recordError,
|
|
959
|
+
"agent_turn_auth_resume_session_record_failed",
|
|
960
|
+
args,
|
|
961
|
+
{
|
|
962
|
+
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
963
|
+
"app.ai.resume_next_slice_id": nextSliceId
|
|
964
|
+
},
|
|
965
|
+
"Failed to persist auth session record before retry"
|
|
966
|
+
);
|
|
807
967
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
// src/chat/capabilities/factory.ts
|
|
811
|
-
var sandboxEgressRouters = /* @__PURE__ */ new WeakMap();
|
|
812
|
-
function createUserTokenStore() {
|
|
813
|
-
return new StateAdapterTokenStore(getStateAdapter());
|
|
968
|
+
return void 0;
|
|
814
969
|
}
|
|
815
|
-
function
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
970
|
+
async function persistTimeoutSessionRecord(args) {
|
|
971
|
+
const nextSliceId = args.currentSliceId + 1;
|
|
972
|
+
try {
|
|
973
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
974
|
+
args.conversationId,
|
|
975
|
+
args.sessionId
|
|
976
|
+
);
|
|
977
|
+
const piMessages = resumableBoundary(
|
|
978
|
+
args.messages,
|
|
979
|
+
latestSessionRecord?.piMessages
|
|
980
|
+
);
|
|
981
|
+
if (piMessages.length === 0 || !isContinuableBoundary(piMessages)) {
|
|
982
|
+
return void 0;
|
|
822
983
|
}
|
|
823
|
-
|
|
824
|
-
|
|
984
|
+
const cumulativeDurationMs = addDurationMs(
|
|
985
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
986
|
+
args.currentDurationMs
|
|
987
|
+
);
|
|
988
|
+
const cumulativeUsage = addAgentTurnUsage(
|
|
989
|
+
latestSessionRecord?.cumulativeUsage,
|
|
990
|
+
args.currentUsage
|
|
991
|
+
);
|
|
992
|
+
if (nextSliceId > AGENT_CONTINUE_MAX_SLICES) {
|
|
993
|
+
return await upsertAgentTurnSessionRecord({
|
|
994
|
+
...args.channelName ?? latestSessionRecord?.channelName ? {
|
|
995
|
+
channelName: args.channelName ?? latestSessionRecord?.channelName
|
|
996
|
+
} : {},
|
|
997
|
+
conversationId: args.conversationId,
|
|
998
|
+
cumulativeDurationMs,
|
|
999
|
+
cumulativeUsage,
|
|
1000
|
+
...args.destination ?? latestSessionRecord?.destination ? {
|
|
1001
|
+
destination: args.destination ?? latestSessionRecord?.destination
|
|
1002
|
+
} : {},
|
|
1003
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1004
|
+
sessionId: args.sessionId,
|
|
1005
|
+
sliceId: args.currentSliceId,
|
|
1006
|
+
state: "failed",
|
|
1007
|
+
piMessages,
|
|
1008
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1009
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1010
|
+
resumeReason: "timeout",
|
|
1011
|
+
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
1012
|
+
errorMessage: `Agent continuation exceeded slice limit (${AGENT_CONTINUE_MAX_SLICES})`,
|
|
1013
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
1014
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
return await upsertAgentTurnSessionRecord({
|
|
1018
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1019
|
+
conversationId: args.conversationId,
|
|
1020
|
+
cumulativeDurationMs,
|
|
1021
|
+
cumulativeUsage,
|
|
1022
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1023
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1024
|
+
sessionId: args.sessionId,
|
|
1025
|
+
sliceId: nextSliceId,
|
|
1026
|
+
state: "awaiting_resume",
|
|
1027
|
+
piMessages,
|
|
1028
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1029
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1030
|
+
resumeReason: "timeout",
|
|
1031
|
+
resumedFromSliceId: args.currentSliceId,
|
|
1032
|
+
errorMessage: args.errorMessage,
|
|
1033
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
1034
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
825
1035
|
});
|
|
1036
|
+
} catch (recordError) {
|
|
1037
|
+
logSessionRecordError(
|
|
1038
|
+
recordError,
|
|
1039
|
+
"agent_continue_session_record_failed",
|
|
1040
|
+
args,
|
|
1041
|
+
{
|
|
1042
|
+
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
1043
|
+
"app.ai.resume_next_slice_id": nextSliceId
|
|
1044
|
+
},
|
|
1045
|
+
"Failed to persist session record before scheduling agent continuation"
|
|
1046
|
+
);
|
|
1047
|
+
return void 0;
|
|
826
1048
|
}
|
|
827
|
-
return new ProviderCredentialRouter({ brokersByProvider });
|
|
828
1049
|
}
|
|
829
|
-
function
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
new StateAdapterTokenStore(stateAdapter)
|
|
1050
|
+
async function persistYieldSessionRecord(args) {
|
|
1051
|
+
try {
|
|
1052
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
1053
|
+
args.conversationId,
|
|
1054
|
+
args.sessionId
|
|
835
1055
|
);
|
|
836
|
-
|
|
1056
|
+
const piMessages = resumableBoundary(
|
|
1057
|
+
args.messages,
|
|
1058
|
+
latestSessionRecord?.piMessages
|
|
1059
|
+
);
|
|
1060
|
+
if (piMessages.length === 0 || !isContinuableBoundary(piMessages)) {
|
|
1061
|
+
return void 0;
|
|
1062
|
+
}
|
|
1063
|
+
return await upsertAgentTurnSessionRecord({
|
|
1064
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1065
|
+
conversationId: args.conversationId,
|
|
1066
|
+
cumulativeDurationMs: addDurationMs(
|
|
1067
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
1068
|
+
args.currentDurationMs
|
|
1069
|
+
),
|
|
1070
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
1071
|
+
latestSessionRecord?.cumulativeUsage,
|
|
1072
|
+
args.currentUsage
|
|
1073
|
+
),
|
|
1074
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1075
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1076
|
+
sessionId: args.sessionId,
|
|
1077
|
+
sliceId: args.currentSliceId,
|
|
1078
|
+
state: "awaiting_resume",
|
|
1079
|
+
piMessages,
|
|
1080
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1081
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1082
|
+
resumeReason: "yield",
|
|
1083
|
+
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
1084
|
+
errorMessage: args.errorMessage,
|
|
1085
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
1086
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1087
|
+
});
|
|
1088
|
+
} catch (recordError) {
|
|
1089
|
+
logSessionRecordError(
|
|
1090
|
+
recordError,
|
|
1091
|
+
"agent_turn_yield_session_record_failed",
|
|
1092
|
+
args,
|
|
1093
|
+
{
|
|
1094
|
+
"app.ai.resume_slice_id": args.currentSliceId
|
|
1095
|
+
},
|
|
1096
|
+
"Failed to persist cooperative yield session record"
|
|
1097
|
+
);
|
|
1098
|
+
return void 0;
|
|
837
1099
|
}
|
|
838
|
-
return router;
|
|
839
|
-
}
|
|
840
|
-
async function issueProviderCredentialLease(input) {
|
|
841
|
-
return await getSandboxEgressRouter().issue(input);
|
|
842
1100
|
}
|
|
843
1101
|
|
|
844
|
-
// src/chat/
|
|
845
|
-
import {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1102
|
+
// src/chat/respond.ts
|
|
1103
|
+
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
1104
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
1105
|
+
import { z as z4 } from "zod";
|
|
1106
|
+
|
|
1107
|
+
// src/chat/capabilities/catalog.ts
|
|
1108
|
+
var cachedCatalog;
|
|
1109
|
+
function getCapabilityCatalog() {
|
|
1110
|
+
const signature = pluginCatalogRuntime.getSignature();
|
|
1111
|
+
if (cachedCatalog?.signature === signature) return cachedCatalog;
|
|
1112
|
+
const providers = pluginCatalogRuntime.getCapabilityProviders();
|
|
1113
|
+
const capabilityToProvider = /* @__PURE__ */ new Map();
|
|
1114
|
+
for (const provider of providers) {
|
|
1115
|
+
for (const capability of provider.capabilities) {
|
|
1116
|
+
if (capabilityToProvider.has(capability)) {
|
|
1117
|
+
throw new Error(
|
|
1118
|
+
`Duplicate capability registration for "${capability}"`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
capabilityToProvider.set(capability, provider);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
cachedCatalog = { signature, providers, capabilityToProvider };
|
|
1125
|
+
return cachedCatalog;
|
|
1126
|
+
}
|
|
1127
|
+
var catalogLogged = false;
|
|
1128
|
+
function logCapabilityCatalogLoadedOnce() {
|
|
1129
|
+
if (catalogLogged) return;
|
|
1130
|
+
catalogLogged = true;
|
|
1131
|
+
const { providers } = getCapabilityCatalog();
|
|
1132
|
+
const capabilityNames = providers.flatMap((p) => p.capabilities).sort();
|
|
1133
|
+
const configKeys = [
|
|
1134
|
+
...new Set(providers.flatMap((p) => p.configKeys))
|
|
1135
|
+
].sort();
|
|
1136
|
+
logInfo(
|
|
1137
|
+
"capability_catalog_loaded",
|
|
1138
|
+
{},
|
|
1139
|
+
{
|
|
1140
|
+
"app.capability.providers": providers.map((p) => p.provider),
|
|
1141
|
+
"app.capability.count": capabilityNames.length,
|
|
1142
|
+
"app.capability.names": capabilityNames,
|
|
1143
|
+
"app.config.key_count": configKeys.length,
|
|
1144
|
+
"app.config.keys": configKeys
|
|
1145
|
+
},
|
|
1146
|
+
"Loaded capability provider catalog"
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/chat/capabilities/router.ts
|
|
1151
|
+
var ProviderCredentialRouter = class {
|
|
1152
|
+
brokersByProvider;
|
|
1153
|
+
constructor(input) {
|
|
1154
|
+
this.brokersByProvider = input.brokersByProvider;
|
|
1155
|
+
}
|
|
1156
|
+
async issue(input) {
|
|
1157
|
+
const broker = this.brokersByProvider[input.provider];
|
|
1158
|
+
if (!broker) {
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
`No credential broker registered for provider: ${input.provider}`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return await broker.issue({
|
|
1164
|
+
context: input.context,
|
|
1165
|
+
reason: input.reason
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
// src/chat/credentials/user-token-store.ts
|
|
1171
|
+
import {
|
|
1172
|
+
pluginStoredTokensSchema
|
|
1173
|
+
} from "@sentry/junior-plugin-api";
|
|
1174
|
+
var storedTokensSchema = pluginStoredTokensSchema;
|
|
1175
|
+
|
|
1176
|
+
// src/chat/credentials/state-adapter-token-store.ts
|
|
1177
|
+
var KEY_PREFIX = "oauth-token";
|
|
1178
|
+
var BUFFER_MS = 24 * 60 * 60 * 1e3;
|
|
1179
|
+
var LONG_LIVED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
1180
|
+
var REFRESH_LOCK_WAIT_MS = 3e4;
|
|
1181
|
+
var REFRESH_LOCK_RETRY_MS = 100;
|
|
1182
|
+
function tokenKey(userId, provider) {
|
|
1183
|
+
return `${KEY_PREFIX}:${userId}:${provider}`;
|
|
1184
|
+
}
|
|
1185
|
+
function refreshLockKey(userId, provider) {
|
|
1186
|
+
return `${tokenKey(userId, provider)}:refresh`;
|
|
1187
|
+
}
|
|
1188
|
+
async function sleep(ms) {
|
|
1189
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1190
|
+
}
|
|
1191
|
+
var StateAdapterTokenStore = class {
|
|
1192
|
+
state;
|
|
1193
|
+
constructor(stateAdapter) {
|
|
1194
|
+
this.state = stateAdapter;
|
|
1195
|
+
}
|
|
1196
|
+
async get(userId, provider) {
|
|
1197
|
+
const stored = await this.state.get(tokenKey(userId, provider));
|
|
1198
|
+
return stored === null || stored === void 0 ? void 0 : storedTokensSchema.parse(stored);
|
|
1199
|
+
}
|
|
1200
|
+
async set(userId, provider, tokens) {
|
|
1201
|
+
const parsed = storedTokensSchema.parse(tokens);
|
|
1202
|
+
const expiresAt = parsed.refreshTokenExpiresAt ?? parsed.expiresAt;
|
|
1203
|
+
const ttlMs2 = expiresAt ? Math.max(expiresAt - Date.now() + BUFFER_MS, BUFFER_MS) : LONG_LIVED_TTL_MS;
|
|
1204
|
+
await this.state.set(tokenKey(userId, provider), parsed, ttlMs2);
|
|
1205
|
+
}
|
|
1206
|
+
async delete(userId, provider) {
|
|
1207
|
+
await this.state.delete(tokenKey(userId, provider));
|
|
1208
|
+
}
|
|
1209
|
+
/** Wait for the per-slot refresh gate so rotated refresh tokens are used once. */
|
|
1210
|
+
async withRefresh(userId, provider, callback) {
|
|
1211
|
+
const lockKey = refreshLockKey(userId, provider);
|
|
1212
|
+
const deadline = Date.now() + REFRESH_LOCK_WAIT_MS;
|
|
1213
|
+
while (true) {
|
|
1214
|
+
const lock = await acquireActiveLock(this.state, lockKey);
|
|
1215
|
+
if (lock) {
|
|
1216
|
+
try {
|
|
1217
|
+
return await callback();
|
|
1218
|
+
} finally {
|
|
1219
|
+
await this.state.releaseLock(lock);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (Date.now() >= deadline) {
|
|
1223
|
+
throw new Error(`Could not acquire OAuth token refresh lock`);
|
|
1224
|
+
}
|
|
1225
|
+
await sleep(REFRESH_LOCK_RETRY_MS);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
// src/chat/capabilities/factory.ts
|
|
1231
|
+
var sandboxEgressRouters = /* @__PURE__ */ new WeakMap();
|
|
1232
|
+
function createUserTokenStore() {
|
|
1233
|
+
return new StateAdapterTokenStore(getStateAdapter());
|
|
1234
|
+
}
|
|
1235
|
+
function createProviderCredentialRouter(userTokenStore) {
|
|
1236
|
+
logCapabilityCatalogLoadedOnce();
|
|
1237
|
+
const brokersByProvider = {};
|
|
1238
|
+
for (const plugin of pluginCatalogRuntime.getProviders()) {
|
|
1239
|
+
const { name } = plugin.manifest;
|
|
1240
|
+
if (!plugin.manifest.credentials && !plugin.manifest.apiHeaders) {
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
brokersByProvider[name] = pluginCatalogRuntime.createBroker(name, {
|
|
1244
|
+
userTokenStore
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
return new ProviderCredentialRouter({ brokersByProvider });
|
|
1248
|
+
}
|
|
1249
|
+
function getSandboxEgressRouter() {
|
|
1250
|
+
const stateAdapter = getStateAdapter();
|
|
1251
|
+
let router = sandboxEgressRouters.get(stateAdapter);
|
|
1252
|
+
if (!router) {
|
|
1253
|
+
router = createProviderCredentialRouter(
|
|
1254
|
+
new StateAdapterTokenStore(stateAdapter)
|
|
1255
|
+
);
|
|
1256
|
+
sandboxEgressRouters.set(stateAdapter, router);
|
|
1257
|
+
}
|
|
1258
|
+
return router;
|
|
1259
|
+
}
|
|
1260
|
+
async function issueProviderCredentialLease(input) {
|
|
1261
|
+
return await getSandboxEgressRouter().issue(input);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// src/chat/capabilities/jr-rpc-command.ts
|
|
1265
|
+
import { Bash, defineCommand } from "just-bash";
|
|
1266
|
+
function commandResult(input) {
|
|
1267
|
+
let stdout = "";
|
|
1268
|
+
if (typeof input.stdout === "string") {
|
|
1269
|
+
stdout = input.stdout;
|
|
1270
|
+
} else if (input.stdout !== void 0) {
|
|
1271
|
+
stdout = `${JSON.stringify(input.stdout, null, 2)}
|
|
1272
|
+
`;
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
stdout,
|
|
1276
|
+
stderr: input.stderr ?? "",
|
|
1277
|
+
exitCode: input.exitCode
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function requireChannelConfiguration(deps) {
|
|
1281
|
+
if (deps.channelConfiguration) {
|
|
862
1282
|
return { ok: true, configuration: deps.channelConfiguration };
|
|
863
1283
|
}
|
|
864
1284
|
return {
|
|
@@ -4470,6 +4890,7 @@ async function getPermalinkBestEffort(args) {
|
|
|
4470
4890
|
3,
|
|
4471
4891
|
{
|
|
4472
4892
|
action: "chat.getPermalink",
|
|
4893
|
+
idempotent: true,
|
|
4473
4894
|
spanAttributes: {
|
|
4474
4895
|
"app.slack.channel_id": args.channelId,
|
|
4475
4896
|
"app.slack.message_ts": args.messageTs
|
|
@@ -4539,6 +4960,7 @@ async function deleteSlackMessage(input) {
|
|
|
4539
4960
|
3,
|
|
4540
4961
|
{
|
|
4541
4962
|
action: "chat.delete",
|
|
4963
|
+
idempotent: true,
|
|
4542
4964
|
spanAttributes: {
|
|
4543
4965
|
"app.slack.channel_id": channelId,
|
|
4544
4966
|
"app.slack.message_ts": timestamp
|
|
@@ -4647,6 +5069,7 @@ async function addReactionToMessage(input) {
|
|
|
4647
5069
|
3,
|
|
4648
5070
|
{
|
|
4649
5071
|
action: "reactions.add",
|
|
5072
|
+
idempotent: true,
|
|
4650
5073
|
spanAttributes: {
|
|
4651
5074
|
"app.slack.channel_id": channelId,
|
|
4652
5075
|
"app.slack.message_ts": timestamp,
|
|
@@ -4685,6 +5108,7 @@ async function removeReactionFromMessage(input) {
|
|
|
4685
5108
|
3,
|
|
4686
5109
|
{
|
|
4687
5110
|
action: "reactions.remove",
|
|
5111
|
+
idempotent: true,
|
|
4688
5112
|
spanAttributes: {
|
|
4689
5113
|
"app.slack.channel_id": channelId,
|
|
4690
5114
|
"app.slack.message_ts": timestamp,
|
|
@@ -5750,6 +6174,31 @@ function createSlackListUpdateItemTool(state) {
|
|
|
5750
6174
|
// src/chat/tools/slack/thread-read.ts
|
|
5751
6175
|
import { Type as Type19 } from "@sinclair/typebox";
|
|
5752
6176
|
|
|
6177
|
+
// src/chat/tools/slack/channel-access.ts
|
|
6178
|
+
async function checkSlackChannelReadAccess(args) {
|
|
6179
|
+
const target = normalizeSlackConversationId(args.targetChannelId);
|
|
6180
|
+
if (!target) {
|
|
6181
|
+
return { allowed: false, error: "Invalid Slack channel ID." };
|
|
6182
|
+
}
|
|
6183
|
+
const currentChannels = args.currentChannelIds.map((channelId) => normalizeSlackConversationId(channelId)).filter((channelId) => Boolean(channelId));
|
|
6184
|
+
if (currentChannels.includes(target)) {
|
|
6185
|
+
return { allowed: true };
|
|
6186
|
+
}
|
|
6187
|
+
const store = args.store ?? getConversationStore();
|
|
6188
|
+
const visibility = await store.getDestinationVisibility({
|
|
6189
|
+
provider: "slack",
|
|
6190
|
+
providerTenantId: args.teamId,
|
|
6191
|
+
providerDestinationId: target
|
|
6192
|
+
});
|
|
6193
|
+
if (visibility === "public") {
|
|
6194
|
+
return { allowed: true };
|
|
6195
|
+
}
|
|
6196
|
+
return {
|
|
6197
|
+
allowed: false,
|
|
6198
|
+
error: "Cannot read this Slack conversation: only the current conversation or public channels Junior has seen in this workspace are readable."
|
|
6199
|
+
};
|
|
6200
|
+
}
|
|
6201
|
+
|
|
5753
6202
|
// src/chat/tools/slack/slack-message-url.ts
|
|
5754
6203
|
var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
|
|
5755
6204
|
var ARCHIVE_PATH_PATTERN = /^\/archives\/([CDG][A-Z0-9]+)\/p(\d{10})(\d{6})$/;
|
|
@@ -5909,26 +6358,9 @@ function truncateMessages(messages, maxChars) {
|
|
|
5909
6358
|
}
|
|
5910
6359
|
return { messages: kept, omitted: messages.length - kept.length };
|
|
5911
6360
|
}
|
|
5912
|
-
function
|
|
5913
|
-
const target = normalizeSlackConversationId(targetChannelId);
|
|
5914
|
-
const current = normalizeSlackConversationId(currentChannelId);
|
|
5915
|
-
if (!target) {
|
|
5916
|
-
return { allowed: false, error: "Invalid Slack channel ID." };
|
|
5917
|
-
}
|
|
5918
|
-
if (target.startsWith("C")) {
|
|
5919
|
-
return { allowed: true };
|
|
5920
|
-
}
|
|
5921
|
-
if (target === current) {
|
|
5922
|
-
return { allowed: true };
|
|
5923
|
-
}
|
|
5924
|
-
return {
|
|
5925
|
-
allowed: false,
|
|
5926
|
-
error: "Cannot read private channels or DMs unless the link is from the current conversation."
|
|
5927
|
-
};
|
|
5928
|
-
}
|
|
5929
|
-
function createSlackThreadReadTool(context) {
|
|
6361
|
+
function createSlackThreadReadTool(context, deps = {}) {
|
|
5930
6362
|
return tool({
|
|
5931
|
-
description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context.
|
|
6363
|
+
description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context. Only the current conversation and public channels Junior has seen in this workspace are readable.",
|
|
5932
6364
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5933
6365
|
inputSchema: Type19.Object({
|
|
5934
6366
|
url: Type19.Optional(
|
|
@@ -5988,10 +6420,15 @@ function createSlackThreadReadTool(context) {
|
|
|
5988
6420
|
error: "Provide either a Slack message `url` or both `channel_id` and `ts`."
|
|
5989
6421
|
};
|
|
5990
6422
|
}
|
|
5991
|
-
const access =
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
6423
|
+
const access = await checkSlackChannelReadAccess({
|
|
6424
|
+
currentChannelIds: [
|
|
6425
|
+
context.destinationChannelId,
|
|
6426
|
+
context.sourceChannelId
|
|
6427
|
+
],
|
|
6428
|
+
store: deps.visibilityStore,
|
|
6429
|
+
targetChannelId: channelId,
|
|
6430
|
+
teamId: context.teamId
|
|
6431
|
+
});
|
|
5995
6432
|
if (!access.allowed) {
|
|
5996
6433
|
return {
|
|
5997
6434
|
ok: false,
|
|
@@ -10860,513 +11297,133 @@ function buildClassifierPrompt(args) {
|
|
|
10860
11297
|
sections.push(
|
|
10861
11298
|
"<thread-background>",
|
|
10862
11299
|
contextText,
|
|
10863
|
-
"</thread-background>",
|
|
10864
|
-
""
|
|
10865
|
-
);
|
|
10866
|
-
}
|
|
10867
|
-
}
|
|
10868
|
-
sections.push(renderCurrentInstruction(args.messageText.trim() || "[empty]"));
|
|
10869
|
-
for (const block of args.currentTurnBlocks ?? []) {
|
|
10870
|
-
const trimmed = block.trim();
|
|
10871
|
-
if (!trimmed) {
|
|
10872
|
-
continue;
|
|
10873
|
-
}
|
|
10874
|
-
sections.push("", trimmed);
|
|
10875
|
-
}
|
|
10876
|
-
return sections.join("\n");
|
|
10877
|
-
}
|
|
10878
|
-
async function selectTurnThinkingLevel(args) {
|
|
10879
|
-
const trimmedContext = trimContextForRouter(args.conversationContext);
|
|
10880
|
-
const instructionLength = args.messageText.trim().length;
|
|
10881
|
-
const turnBlockCount = (args.currentTurnBlocks ?? []).filter(
|
|
10882
|
-
(block) => block.trim().length > 0
|
|
10883
|
-
).length;
|
|
10884
|
-
const prompt = buildClassifierPrompt({
|
|
10885
|
-
conversationContext: trimmedContext,
|
|
10886
|
-
currentTurnBlocks: args.currentTurnBlocks,
|
|
10887
|
-
messageText: args.messageText
|
|
10888
|
-
});
|
|
10889
|
-
const logContext = {
|
|
10890
|
-
slackThreadId: args.context?.threadId,
|
|
10891
|
-
slackChannelId: args.context?.channelId,
|
|
10892
|
-
slackUserId: args.context?.requesterId,
|
|
10893
|
-
runId: args.context?.runId,
|
|
10894
|
-
modelId: args.fastModelId
|
|
10895
|
-
};
|
|
10896
|
-
return withSpan(
|
|
10897
|
-
"chat.route_thinking",
|
|
10898
|
-
"chat.route_thinking",
|
|
10899
|
-
logContext,
|
|
10900
|
-
async () => {
|
|
10901
|
-
setSpanAttributes({
|
|
10902
|
-
"app.ai.router.prompt_char_count": prompt.length,
|
|
10903
|
-
"app.ai.router.instruction_char_count": instructionLength,
|
|
10904
|
-
"app.ai.router.context_char_count": trimmedContext?.originalCharCount ?? 0,
|
|
10905
|
-
"app.ai.router.context_trimmed": trimmedContext?.truncated ?? false,
|
|
10906
|
-
"app.ai.router.turn_block_count": turnBlockCount
|
|
10907
|
-
});
|
|
10908
|
-
const selection = await classifyTurn({
|
|
10909
|
-
completeObject: args.completeObject,
|
|
10910
|
-
fastModelId: args.fastModelId,
|
|
10911
|
-
metadata: {
|
|
10912
|
-
modelId: args.fastModelId,
|
|
10913
|
-
threadId: args.context?.threadId ?? "",
|
|
10914
|
-
channelId: args.context?.channelId ?? "",
|
|
10915
|
-
requesterId: args.context?.requesterId ?? "",
|
|
10916
|
-
runId: args.context?.runId ?? ""
|
|
10917
|
-
},
|
|
10918
|
-
prompt
|
|
10919
|
-
});
|
|
10920
|
-
const normalizedSelection = applyThinkingFloor(selection, {
|
|
10921
|
-
minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
|
|
10922
|
-
});
|
|
10923
|
-
setSpanAttributes({
|
|
10924
|
-
"app.ai.thinking_level": normalizedSelection.thinkingLevel,
|
|
10925
|
-
"app.ai.thinking_level_reason": normalizedSelection.reason,
|
|
10926
|
-
...normalizedSelection.confidence !== void 0 ? {
|
|
10927
|
-
"app.ai.thinking_level_confidence": normalizedSelection.confidence
|
|
10928
|
-
} : {}
|
|
10929
|
-
});
|
|
10930
|
-
return normalizedSelection;
|
|
10931
|
-
}
|
|
10932
|
-
);
|
|
10933
|
-
}
|
|
10934
|
-
function applyThinkingFloor(selection, args) {
|
|
10935
|
-
const minimum = args.minimum;
|
|
10936
|
-
if (!minimum || selection.thinkingLevel === "none" || THINKING_LEVEL_RANK[selection.thinkingLevel] >= THINKING_LEVEL_RANK[minimum]) {
|
|
10937
|
-
return selection;
|
|
10938
|
-
}
|
|
10939
|
-
return {
|
|
10940
|
-
...selection,
|
|
10941
|
-
thinkingLevel: minimum,
|
|
10942
|
-
reason: `thinking_floor:${minimum}:${selection.reason}`
|
|
10943
|
-
};
|
|
10944
|
-
}
|
|
10945
|
-
async function classifyTurn(args) {
|
|
10946
|
-
try {
|
|
10947
|
-
const result = await args.completeObject({
|
|
10948
|
-
modelId: args.fastModelId,
|
|
10949
|
-
schema: turnExecutionProfileSchema,
|
|
10950
|
-
maxTokens: 120,
|
|
10951
|
-
metadata: args.metadata,
|
|
10952
|
-
prompt: args.prompt,
|
|
10953
|
-
thinkingLevel: "low",
|
|
10954
|
-
system: buildClassifierSystemPrompt(),
|
|
10955
|
-
temperature: 0
|
|
10956
|
-
});
|
|
10957
|
-
const parsed = turnExecutionProfileSchema.parse(result.object);
|
|
10958
|
-
const reason = parsed.reason.trim();
|
|
10959
|
-
if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
|
|
10960
|
-
return {
|
|
10961
|
-
confidence: parsed.confidence,
|
|
10962
|
-
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
10963
|
-
reason: `low_confidence_medium_default:${reason}`
|
|
10964
|
-
};
|
|
10965
|
-
}
|
|
10966
|
-
return {
|
|
10967
|
-
confidence: parsed.confidence,
|
|
10968
|
-
thinkingLevel: parsed.thinking_level,
|
|
10969
|
-
reason
|
|
10970
|
-
};
|
|
10971
|
-
} catch {
|
|
10972
|
-
return {
|
|
10973
|
-
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
10974
|
-
reason: "classifier_error_default"
|
|
10975
|
-
};
|
|
10976
|
-
}
|
|
10977
|
-
}
|
|
10978
|
-
function toAgentThinkingLevel(level) {
|
|
10979
|
-
switch (level) {
|
|
10980
|
-
case "none":
|
|
10981
|
-
return "off";
|
|
10982
|
-
case "low":
|
|
10983
|
-
return "low";
|
|
10984
|
-
case "medium":
|
|
10985
|
-
return "medium";
|
|
10986
|
-
case "high":
|
|
10987
|
-
return "high";
|
|
10988
|
-
case "xhigh":
|
|
10989
|
-
return "xhigh";
|
|
10990
|
-
}
|
|
10991
|
-
}
|
|
10992
|
-
|
|
10993
|
-
// src/chat/usage.ts
|
|
10994
|
-
var COMPONENT_USAGE_FIELDS = [
|
|
10995
|
-
"inputTokens",
|
|
10996
|
-
"outputTokens",
|
|
10997
|
-
"cachedInputTokens",
|
|
10998
|
-
"cacheCreationTokens"
|
|
10999
|
-
];
|
|
11000
|
-
function hasAgentTurnUsage(usage) {
|
|
11001
|
-
return Boolean(
|
|
11002
|
-
usage && Object.values(usage).some(
|
|
11003
|
-
(value) => typeof value === "number" && Number.isFinite(value)
|
|
11004
|
-
)
|
|
11005
|
-
);
|
|
11006
|
-
}
|
|
11007
|
-
function getFiniteCount(value) {
|
|
11008
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
11009
|
-
}
|
|
11010
|
-
function getComponentTotal(usage) {
|
|
11011
|
-
let total;
|
|
11012
|
-
for (const field of COMPONENT_USAGE_FIELDS) {
|
|
11013
|
-
const value = getFiniteCount(usage[field]);
|
|
11014
|
-
if (value === void 0) continue;
|
|
11015
|
-
total = (total ?? 0) + value;
|
|
11016
|
-
}
|
|
11017
|
-
return total;
|
|
11018
|
-
}
|
|
11019
|
-
function addAgentTurnUsage(...usages) {
|
|
11020
|
-
const components = {};
|
|
11021
|
-
let componentTotal;
|
|
11022
|
-
let totalOnlyTokens;
|
|
11023
|
-
for (const usage of usages) {
|
|
11024
|
-
if (!usage) continue;
|
|
11025
|
-
const usageComponentTotal = getComponentTotal(usage);
|
|
11026
|
-
if (usageComponentTotal !== void 0) {
|
|
11027
|
-
componentTotal = (componentTotal ?? 0) + usageComponentTotal;
|
|
11028
|
-
for (const field of COMPONENT_USAGE_FIELDS) {
|
|
11029
|
-
const value = getFiniteCount(usage[field]);
|
|
11030
|
-
if (value === void 0) continue;
|
|
11031
|
-
components[field] = (components[field] ?? 0) + value;
|
|
11032
|
-
}
|
|
11033
|
-
continue;
|
|
11034
|
-
}
|
|
11035
|
-
const totalTokens = getFiniteCount(usage.totalTokens);
|
|
11036
|
-
if (totalTokens !== void 0) {
|
|
11037
|
-
totalOnlyTokens = (totalOnlyTokens ?? 0) + totalTokens;
|
|
11038
|
-
}
|
|
11039
|
-
}
|
|
11040
|
-
if (totalOnlyTokens !== void 0) {
|
|
11041
|
-
return {
|
|
11042
|
-
totalTokens: totalOnlyTokens + (componentTotal ?? 0)
|
|
11043
|
-
};
|
|
11044
|
-
}
|
|
11045
|
-
return hasAgentTurnUsage(components) ? components : void 0;
|
|
11046
|
-
}
|
|
11047
|
-
|
|
11048
|
-
// src/chat/services/turn-session-record.ts
|
|
11049
|
-
var AGENT_CONTINUE_MAX_SLICES = 48;
|
|
11050
|
-
function logSessionRecordError(error, eventName, args, attributes, message) {
|
|
11051
|
-
logException(
|
|
11052
|
-
error,
|
|
11053
|
-
eventName,
|
|
11054
|
-
{
|
|
11055
|
-
slackThreadId: args.logContext.threadId,
|
|
11056
|
-
slackUserId: args.logContext.requesterId,
|
|
11057
|
-
slackChannelId: args.logContext.channelId,
|
|
11058
|
-
runId: args.logContext.runId,
|
|
11059
|
-
assistantUserName: args.logContext.assistantUserName,
|
|
11060
|
-
modelId: args.logContext.modelId
|
|
11061
|
-
},
|
|
11062
|
-
{
|
|
11063
|
-
"app.ai.resume_conversation_id": args.conversationId,
|
|
11064
|
-
"app.ai.resume_session_id": args.sessionId,
|
|
11065
|
-
...attributes
|
|
11066
|
-
},
|
|
11067
|
-
message
|
|
11068
|
-
);
|
|
11069
|
-
}
|
|
11070
|
-
function addDurationMs(prior, current) {
|
|
11071
|
-
const total = [prior, current].reduce((sum, value) => {
|
|
11072
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
11073
|
-
return sum;
|
|
11074
|
-
}
|
|
11075
|
-
return (sum ?? 0) + Math.max(0, Math.floor(value));
|
|
11076
|
-
}, void 0);
|
|
11077
|
-
return total;
|
|
11078
|
-
}
|
|
11079
|
-
function isContinuableBoundary(messages) {
|
|
11080
|
-
const lastRole = getPiMessageRole(messages.at(-1));
|
|
11081
|
-
return lastRole === "user" || lastRole === "toolResult";
|
|
11082
|
-
}
|
|
11083
|
-
function resumableBoundary(messages, fallbackMessages) {
|
|
11084
|
-
const current = trimTrailingAssistantMessages(messages);
|
|
11085
|
-
if (current.length > 0 && isContinuableBoundary(current)) {
|
|
11086
|
-
return current;
|
|
11087
|
-
}
|
|
11088
|
-
return trimTrailingAssistantMessages(fallbackMessages ?? []);
|
|
11089
|
-
}
|
|
11090
|
-
async function loadTurnSessionRecord(ctx) {
|
|
11091
|
-
const canUseTurnSession = Boolean(ctx.conversationId && ctx.sessionId);
|
|
11092
|
-
const existingSessionRecord = canUseTurnSession && ctx.conversationId && ctx.sessionId ? await getAgentTurnSessionRecord(ctx.conversationId, ctx.sessionId) : void 0;
|
|
11093
|
-
const hasAwaitingResumeRecord = Boolean(
|
|
11094
|
-
existingSessionRecord && existingSessionRecord.state === "awaiting_resume"
|
|
11095
|
-
);
|
|
11096
|
-
return {
|
|
11097
|
-
canUseTurnSession,
|
|
11098
|
-
resumedFromSessionRecord: hasAwaitingResumeRecord,
|
|
11099
|
-
currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
|
|
11100
|
-
existingSessionRecord
|
|
11101
|
-
};
|
|
11102
|
-
}
|
|
11103
|
-
async function persistRunningSessionRecord(args) {
|
|
11104
|
-
if (args.messages.length === 0 || !isContinuableBoundary(args.messages)) {
|
|
11105
|
-
return false;
|
|
11106
|
-
}
|
|
11107
|
-
try {
|
|
11108
|
-
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
11109
|
-
args.conversationId,
|
|
11110
|
-
args.sessionId
|
|
11111
|
-
);
|
|
11112
|
-
await upsertAgentTurnSessionRecord({
|
|
11113
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
11114
|
-
conversationId: args.conversationId,
|
|
11115
|
-
cumulativeDurationMs: latestSessionRecord?.cumulativeDurationMs,
|
|
11116
|
-
cumulativeUsage: latestSessionRecord?.cumulativeUsage,
|
|
11117
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
11118
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
11119
|
-
sessionId: args.sessionId,
|
|
11120
|
-
sliceId: args.sliceId,
|
|
11121
|
-
state: "running",
|
|
11122
|
-
piMessages: args.messages,
|
|
11123
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11124
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11125
|
-
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11126
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
11127
|
-
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
11128
|
-
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
11129
|
-
} : {}
|
|
11130
|
-
});
|
|
11131
|
-
return true;
|
|
11132
|
-
} catch (recordError) {
|
|
11133
|
-
logSessionRecordError(
|
|
11134
|
-
recordError,
|
|
11135
|
-
"agent_turn_running_session_record_failed",
|
|
11136
|
-
args,
|
|
11137
|
-
{
|
|
11138
|
-
"app.ai.resume_slice_id": args.sliceId
|
|
11139
|
-
},
|
|
11140
|
-
"Failed to persist running turn session record"
|
|
11141
|
-
);
|
|
11142
|
-
return false;
|
|
11143
|
-
}
|
|
11144
|
-
}
|
|
11145
|
-
async function persistCompletedSessionRecord(args) {
|
|
11146
|
-
try {
|
|
11147
|
-
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
11148
|
-
args.conversationId,
|
|
11149
|
-
args.sessionId
|
|
11150
|
-
);
|
|
11151
|
-
await upsertAgentTurnSessionRecord({
|
|
11152
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
11153
|
-
conversationId: args.conversationId,
|
|
11154
|
-
cumulativeDurationMs: addDurationMs(
|
|
11155
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
11156
|
-
args.currentDurationMs
|
|
11157
|
-
),
|
|
11158
|
-
cumulativeUsage: addAgentTurnUsage(
|
|
11159
|
-
latestSessionRecord?.cumulativeUsage,
|
|
11160
|
-
args.currentUsage
|
|
11161
|
-
),
|
|
11162
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
11163
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
11164
|
-
sessionId: args.sessionId,
|
|
11165
|
-
sliceId: args.sliceId,
|
|
11166
|
-
state: "completed",
|
|
11167
|
-
piMessages: args.allMessages,
|
|
11168
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11169
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11170
|
-
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11171
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
11172
|
-
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
11173
|
-
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
11174
|
-
} : {}
|
|
11175
|
-
});
|
|
11176
|
-
} catch (recordError) {
|
|
11177
|
-
logSessionRecordError(
|
|
11178
|
-
recordError,
|
|
11179
|
-
"agent_turn_completed_session_record_failed",
|
|
11180
|
-
args,
|
|
11181
|
-
{
|
|
11182
|
-
"app.ai.resume_slice_id": args.sliceId
|
|
11183
|
-
},
|
|
11184
|
-
"Failed to persist completed turn session record"
|
|
11185
|
-
);
|
|
11186
|
-
}
|
|
11187
|
-
}
|
|
11188
|
-
async function persistAuthPauseSessionRecord(args) {
|
|
11189
|
-
const nextSliceId = args.currentSliceId + 1;
|
|
11190
|
-
try {
|
|
11191
|
-
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
11192
|
-
args.conversationId,
|
|
11193
|
-
args.sessionId
|
|
11194
|
-
);
|
|
11195
|
-
const piMessages = resumableBoundary(
|
|
11196
|
-
args.messages,
|
|
11197
|
-
latestSessionRecord?.piMessages
|
|
11198
|
-
);
|
|
11199
|
-
if (piMessages.length > 0 && !isContinuableBoundary(piMessages)) {
|
|
11200
|
-
return void 0;
|
|
11201
|
-
}
|
|
11202
|
-
return await upsertAgentTurnSessionRecord({
|
|
11203
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
11204
|
-
conversationId: args.conversationId,
|
|
11205
|
-
cumulativeDurationMs: addDurationMs(
|
|
11206
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
11207
|
-
args.currentDurationMs
|
|
11208
|
-
),
|
|
11209
|
-
cumulativeUsage: addAgentTurnUsage(
|
|
11210
|
-
latestSessionRecord?.cumulativeUsage,
|
|
11211
|
-
args.currentUsage
|
|
11212
|
-
),
|
|
11213
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
11214
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
11215
|
-
sessionId: args.sessionId,
|
|
11216
|
-
sliceId: nextSliceId,
|
|
11217
|
-
state: "awaiting_resume",
|
|
11218
|
-
piMessages,
|
|
11219
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11220
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11221
|
-
resumeReason: "auth",
|
|
11222
|
-
resumedFromSliceId: args.currentSliceId,
|
|
11223
|
-
errorMessage: args.errorMessage,
|
|
11224
|
-
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11225
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
11226
|
-
});
|
|
11227
|
-
} catch (recordError) {
|
|
11228
|
-
logSessionRecordError(
|
|
11229
|
-
recordError,
|
|
11230
|
-
"agent_turn_auth_resume_session_record_failed",
|
|
11231
|
-
args,
|
|
11232
|
-
{
|
|
11233
|
-
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
11234
|
-
"app.ai.resume_next_slice_id": nextSliceId
|
|
11235
|
-
},
|
|
11236
|
-
"Failed to persist auth session record before retry"
|
|
11237
|
-
);
|
|
11238
|
-
}
|
|
11239
|
-
return void 0;
|
|
11240
|
-
}
|
|
11241
|
-
async function persistTimeoutSessionRecord(args) {
|
|
11242
|
-
const nextSliceId = args.currentSliceId + 1;
|
|
11243
|
-
try {
|
|
11244
|
-
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
11245
|
-
args.conversationId,
|
|
11246
|
-
args.sessionId
|
|
11247
|
-
);
|
|
11248
|
-
const piMessages = resumableBoundary(
|
|
11249
|
-
args.messages,
|
|
11250
|
-
latestSessionRecord?.piMessages
|
|
11251
|
-
);
|
|
11252
|
-
if (piMessages.length === 0 || !isContinuableBoundary(piMessages)) {
|
|
11253
|
-
return void 0;
|
|
11254
|
-
}
|
|
11255
|
-
const cumulativeDurationMs = addDurationMs(
|
|
11256
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
11257
|
-
args.currentDurationMs
|
|
11258
|
-
);
|
|
11259
|
-
const cumulativeUsage = addAgentTurnUsage(
|
|
11260
|
-
latestSessionRecord?.cumulativeUsage,
|
|
11261
|
-
args.currentUsage
|
|
11262
|
-
);
|
|
11263
|
-
if (nextSliceId > AGENT_CONTINUE_MAX_SLICES) {
|
|
11264
|
-
return await upsertAgentTurnSessionRecord({
|
|
11265
|
-
...args.channelName ?? latestSessionRecord?.channelName ? {
|
|
11266
|
-
channelName: args.channelName ?? latestSessionRecord?.channelName
|
|
11267
|
-
} : {},
|
|
11268
|
-
conversationId: args.conversationId,
|
|
11269
|
-
cumulativeDurationMs,
|
|
11270
|
-
cumulativeUsage,
|
|
11271
|
-
...args.destination ?? latestSessionRecord?.destination ? {
|
|
11272
|
-
destination: args.destination ?? latestSessionRecord?.destination
|
|
11273
|
-
} : {},
|
|
11274
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
11275
|
-
sessionId: args.sessionId,
|
|
11276
|
-
sliceId: args.currentSliceId,
|
|
11277
|
-
state: "failed",
|
|
11278
|
-
piMessages,
|
|
11279
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11280
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11281
|
-
resumeReason: "timeout",
|
|
11282
|
-
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
11283
|
-
errorMessage: `Agent continuation exceeded slice limit (${AGENT_CONTINUE_MAX_SLICES})`,
|
|
11284
|
-
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11285
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
11300
|
+
"</thread-background>",
|
|
11301
|
+
""
|
|
11302
|
+
);
|
|
11303
|
+
}
|
|
11304
|
+
}
|
|
11305
|
+
sections.push(renderCurrentInstruction(args.messageText.trim() || "[empty]"));
|
|
11306
|
+
for (const block of args.currentTurnBlocks ?? []) {
|
|
11307
|
+
const trimmed = block.trim();
|
|
11308
|
+
if (!trimmed) {
|
|
11309
|
+
continue;
|
|
11310
|
+
}
|
|
11311
|
+
sections.push("", trimmed);
|
|
11312
|
+
}
|
|
11313
|
+
return sections.join("\n");
|
|
11314
|
+
}
|
|
11315
|
+
async function selectTurnThinkingLevel(args) {
|
|
11316
|
+
const trimmedContext = trimContextForRouter(args.conversationContext);
|
|
11317
|
+
const instructionLength = args.messageText.trim().length;
|
|
11318
|
+
const turnBlockCount = (args.currentTurnBlocks ?? []).filter(
|
|
11319
|
+
(block) => block.trim().length > 0
|
|
11320
|
+
).length;
|
|
11321
|
+
const prompt = buildClassifierPrompt({
|
|
11322
|
+
conversationContext: trimmedContext,
|
|
11323
|
+
currentTurnBlocks: args.currentTurnBlocks,
|
|
11324
|
+
messageText: args.messageText
|
|
11325
|
+
});
|
|
11326
|
+
const logContext = {
|
|
11327
|
+
slackThreadId: args.context?.threadId,
|
|
11328
|
+
slackChannelId: args.context?.channelId,
|
|
11329
|
+
slackUserId: args.context?.requesterId,
|
|
11330
|
+
runId: args.context?.runId,
|
|
11331
|
+
modelId: args.fastModelId
|
|
11332
|
+
};
|
|
11333
|
+
return withSpan(
|
|
11334
|
+
"chat.route_thinking",
|
|
11335
|
+
"chat.route_thinking",
|
|
11336
|
+
logContext,
|
|
11337
|
+
async () => {
|
|
11338
|
+
setSpanAttributes({
|
|
11339
|
+
"app.ai.router.prompt_char_count": prompt.length,
|
|
11340
|
+
"app.ai.router.instruction_char_count": instructionLength,
|
|
11341
|
+
"app.ai.router.context_char_count": trimmedContext?.originalCharCount ?? 0,
|
|
11342
|
+
"app.ai.router.context_trimmed": trimmedContext?.truncated ?? false,
|
|
11343
|
+
"app.ai.router.turn_block_count": turnBlockCount
|
|
11344
|
+
});
|
|
11345
|
+
const selection = await classifyTurn({
|
|
11346
|
+
completeObject: args.completeObject,
|
|
11347
|
+
fastModelId: args.fastModelId,
|
|
11348
|
+
metadata: {
|
|
11349
|
+
modelId: args.fastModelId,
|
|
11350
|
+
threadId: args.context?.threadId ?? "",
|
|
11351
|
+
channelId: args.context?.channelId ?? "",
|
|
11352
|
+
requesterId: args.context?.requesterId ?? "",
|
|
11353
|
+
runId: args.context?.runId ?? ""
|
|
11354
|
+
},
|
|
11355
|
+
prompt
|
|
11356
|
+
});
|
|
11357
|
+
const normalizedSelection = applyThinkingFloor(selection, {
|
|
11358
|
+
minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
|
|
11359
|
+
});
|
|
11360
|
+
setSpanAttributes({
|
|
11361
|
+
"app.ai.thinking_level": normalizedSelection.thinkingLevel,
|
|
11362
|
+
"app.ai.thinking_level_reason": normalizedSelection.reason,
|
|
11363
|
+
...normalizedSelection.confidence !== void 0 ? {
|
|
11364
|
+
"app.ai.thinking_level_confidence": normalizedSelection.confidence
|
|
11365
|
+
} : {}
|
|
11286
11366
|
});
|
|
11367
|
+
return normalizedSelection;
|
|
11287
11368
|
}
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
11295
|
-
sessionId: args.sessionId,
|
|
11296
|
-
sliceId: nextSliceId,
|
|
11297
|
-
state: "awaiting_resume",
|
|
11298
|
-
piMessages,
|
|
11299
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11300
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11301
|
-
resumeReason: "timeout",
|
|
11302
|
-
resumedFromSliceId: args.currentSliceId,
|
|
11303
|
-
errorMessage: args.errorMessage,
|
|
11304
|
-
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11305
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
11306
|
-
});
|
|
11307
|
-
} catch (recordError) {
|
|
11308
|
-
logSessionRecordError(
|
|
11309
|
-
recordError,
|
|
11310
|
-
"agent_continue_session_record_failed",
|
|
11311
|
-
args,
|
|
11312
|
-
{
|
|
11313
|
-
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
11314
|
-
"app.ai.resume_next_slice_id": nextSliceId
|
|
11315
|
-
},
|
|
11316
|
-
"Failed to persist session record before scheduling agent continuation"
|
|
11317
|
-
);
|
|
11318
|
-
return void 0;
|
|
11369
|
+
);
|
|
11370
|
+
}
|
|
11371
|
+
function applyThinkingFloor(selection, args) {
|
|
11372
|
+
const minimum = args.minimum;
|
|
11373
|
+
if (!minimum || selection.thinkingLevel === "none" || THINKING_LEVEL_RANK[selection.thinkingLevel] >= THINKING_LEVEL_RANK[minimum]) {
|
|
11374
|
+
return selection;
|
|
11319
11375
|
}
|
|
11376
|
+
return {
|
|
11377
|
+
...selection,
|
|
11378
|
+
thinkingLevel: minimum,
|
|
11379
|
+
reason: `thinking_floor:${minimum}:${selection.reason}`
|
|
11380
|
+
};
|
|
11320
11381
|
}
|
|
11321
|
-
async function
|
|
11382
|
+
async function classifyTurn(args) {
|
|
11322
11383
|
try {
|
|
11323
|
-
const
|
|
11324
|
-
args.
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
args.
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
return void 0;
|
|
11333
|
-
}
|
|
11334
|
-
return await upsertAgentTurnSessionRecord({
|
|
11335
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
11336
|
-
conversationId: args.conversationId,
|
|
11337
|
-
cumulativeDurationMs: addDurationMs(
|
|
11338
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
11339
|
-
args.currentDurationMs
|
|
11340
|
-
),
|
|
11341
|
-
cumulativeUsage: addAgentTurnUsage(
|
|
11342
|
-
latestSessionRecord?.cumulativeUsage,
|
|
11343
|
-
args.currentUsage
|
|
11344
|
-
),
|
|
11345
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
11346
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
11347
|
-
sessionId: args.sessionId,
|
|
11348
|
-
sliceId: args.currentSliceId,
|
|
11349
|
-
state: "awaiting_resume",
|
|
11350
|
-
piMessages,
|
|
11351
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
11352
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
11353
|
-
resumeReason: "yield",
|
|
11354
|
-
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
11355
|
-
errorMessage: args.errorMessage,
|
|
11356
|
-
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
11357
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
11384
|
+
const result = await args.completeObject({
|
|
11385
|
+
modelId: args.fastModelId,
|
|
11386
|
+
schema: turnExecutionProfileSchema,
|
|
11387
|
+
maxTokens: 120,
|
|
11388
|
+
metadata: args.metadata,
|
|
11389
|
+
prompt: args.prompt,
|
|
11390
|
+
thinkingLevel: "low",
|
|
11391
|
+
system: buildClassifierSystemPrompt(),
|
|
11392
|
+
temperature: 0
|
|
11358
11393
|
});
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
}
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11394
|
+
const parsed = turnExecutionProfileSchema.parse(result.object);
|
|
11395
|
+
const reason = parsed.reason.trim();
|
|
11396
|
+
if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
|
|
11397
|
+
return {
|
|
11398
|
+
confidence: parsed.confidence,
|
|
11399
|
+
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
11400
|
+
reason: `low_confidence_medium_default:${reason}`
|
|
11401
|
+
};
|
|
11402
|
+
}
|
|
11403
|
+
return {
|
|
11404
|
+
confidence: parsed.confidence,
|
|
11405
|
+
thinkingLevel: parsed.thinking_level,
|
|
11406
|
+
reason
|
|
11407
|
+
};
|
|
11408
|
+
} catch {
|
|
11409
|
+
return {
|
|
11410
|
+
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
11411
|
+
reason: "classifier_error_default"
|
|
11412
|
+
};
|
|
11413
|
+
}
|
|
11414
|
+
}
|
|
11415
|
+
function toAgentThinkingLevel(level) {
|
|
11416
|
+
switch (level) {
|
|
11417
|
+
case "none":
|
|
11418
|
+
return "off";
|
|
11419
|
+
case "low":
|
|
11420
|
+
return "low";
|
|
11421
|
+
case "medium":
|
|
11422
|
+
return "medium";
|
|
11423
|
+
case "high":
|
|
11424
|
+
return "high";
|
|
11425
|
+
case "xhigh":
|
|
11426
|
+
return "xhigh";
|
|
11370
11427
|
}
|
|
11371
11428
|
}
|
|
11372
11429
|
|
|
@@ -13051,7 +13108,10 @@ function legacyTextPartMatchesCurrentText(storedText, currentText) {
|
|
|
13051
13108
|
async function generateAssistantReply(messageText2, context) {
|
|
13052
13109
|
const conversationPrivacy = resolveConversationPrivacy({
|
|
13053
13110
|
channelId: context.correlation?.channelId,
|
|
13054
|
-
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId
|
|
13111
|
+
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
13112
|
+
// Source-confirmed visibility from the live event's channel_type; without
|
|
13113
|
+
// it the turn fails closed to private telemetry capture.
|
|
13114
|
+
visibility: context.slackConversation?.visibility
|
|
13055
13115
|
});
|
|
13056
13116
|
return runWithConversationPrivacy(
|
|
13057
13117
|
conversationPrivacy ?? "private",
|
|
@@ -13214,14 +13274,26 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13214
13274
|
if (!turnSessionState.canUseTurnSession || !sessionConversationId || !sessionId) {
|
|
13215
13275
|
return;
|
|
13216
13276
|
}
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13277
|
+
try {
|
|
13278
|
+
await recordToolExecutionStarted({
|
|
13279
|
+
conversationId: sessionConversationId,
|
|
13280
|
+
sessionId,
|
|
13281
|
+
toolCallId: event.toolCallId,
|
|
13282
|
+
toolName: event.toolName,
|
|
13283
|
+
args: event.args,
|
|
13284
|
+
ttlMs: THREAD_STATE_TTL_MS4
|
|
13285
|
+
});
|
|
13286
|
+
} catch (error) {
|
|
13287
|
+
logException(
|
|
13288
|
+
error,
|
|
13289
|
+
"agent_turn_session_log_append_failed",
|
|
13290
|
+
spanContext,
|
|
13291
|
+
{
|
|
13292
|
+
"gen_ai.tool.name": event.toolName
|
|
13293
|
+
},
|
|
13294
|
+
"Failed to record host-only tool execution start"
|
|
13295
|
+
);
|
|
13296
|
+
}
|
|
13225
13297
|
};
|
|
13226
13298
|
const persistedConfigurationValues = context.channelConfiguration ? await context.channelConfiguration.resolveValues() : {};
|
|
13227
13299
|
configurationValues = {
|
|
@@ -13581,10 +13653,10 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13581
13653
|
);
|
|
13582
13654
|
const hasPromptCheckpoint = resumedFromSessionRecord && existingSessionRecord?.turnStartMessageIndex !== void 0;
|
|
13583
13655
|
const shouldPromptAgent = !resumedFromSessionRecord || !hasPromptCheckpoint;
|
|
13584
|
-
const promptHistoryMessages = shouldPromptAgent
|
|
13656
|
+
const promptHistoryMessages = shouldPromptAgent ? withoutTrailingUncheckpointedUserPrompt(
|
|
13585
13657
|
priorPiMessages,
|
|
13586
13658
|
userContentParts
|
|
13587
|
-
) :
|
|
13659
|
+
) : existingSessionRecord.piMessages;
|
|
13588
13660
|
const needsBootstrapContextForPrompt = shouldPromptAgent && !hasRuntimeTurnContext(promptHistoryMessages);
|
|
13589
13661
|
const systemPromptContributions = await getPluginSystemPromptContributions(toolSource);
|
|
13590
13662
|
const pluginSystemPrompt = buildPluginSystemPromptContributions(
|
|
@@ -13739,12 +13811,12 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13739
13811
|
});
|
|
13740
13812
|
if (steeredMessageCount > 0) {
|
|
13741
13813
|
logInfo(
|
|
13742
|
-
"
|
|
13814
|
+
"agent_turn_steering_messages_accepted",
|
|
13743
13815
|
spanContext,
|
|
13744
13816
|
{
|
|
13745
13817
|
"app.ai.steering_message_count": steeredMessageCount
|
|
13746
13818
|
},
|
|
13747
|
-
"Agent turn steering messages
|
|
13819
|
+
"Agent turn steering messages accepted"
|
|
13748
13820
|
);
|
|
13749
13821
|
}
|
|
13750
13822
|
} catch (error) {
|
|
@@ -13835,10 +13907,10 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13835
13907
|
beforeMessageCount = agent.state.messages.length;
|
|
13836
13908
|
try {
|
|
13837
13909
|
if (resumedFromSessionRecord) {
|
|
13838
|
-
agent.state.messages = shouldPromptAgent ? promptHistoryMessages
|
|
13910
|
+
agent.state.messages = shouldPromptAgent ? promptHistoryMessages : existingSessionRecord.piMessages;
|
|
13839
13911
|
turnStartMessageIndex = existingSessionRecord.turnStartMessageIndex;
|
|
13840
|
-
} else if (
|
|
13841
|
-
agent.state.messages = [...
|
|
13912
|
+
} else if (promptHistoryMessages.length > 0) {
|
|
13913
|
+
agent.state.messages = [...promptHistoryMessages];
|
|
13842
13914
|
}
|
|
13843
13915
|
beforeMessageCount = agent.state.messages.length;
|
|
13844
13916
|
if (shouldPromptAgent) {
|
|
@@ -14006,22 +14078,6 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
14006
14078
|
}
|
|
14007
14079
|
if (turnSessionState.canUseTurnSession && sessionConversationId && sessionId) {
|
|
14008
14080
|
await recordActiveMcpProviders();
|
|
14009
|
-
await persistCompletedSessionRecord({
|
|
14010
|
-
channelName: context.correlation?.channelName,
|
|
14011
|
-
conversationId: sessionConversationId,
|
|
14012
|
-
currentDurationMs: Date.now() - replyStartedAtMs,
|
|
14013
|
-
currentUsage: turnUsage,
|
|
14014
|
-
destination: context.destination,
|
|
14015
|
-
source: runSource,
|
|
14016
|
-
sessionId,
|
|
14017
|
-
sliceId: currentSliceId,
|
|
14018
|
-
allMessages: agent.state.messages,
|
|
14019
|
-
loadedSkillNames: loadedSkillNamesForResume,
|
|
14020
|
-
logContext: sessionRecordLogContext,
|
|
14021
|
-
requester,
|
|
14022
|
-
...surface ? { surface } : {},
|
|
14023
|
-
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {}
|
|
14024
|
-
});
|
|
14025
14081
|
}
|
|
14026
14082
|
return buildTurnResult({
|
|
14027
14083
|
newMessages,
|
|
@@ -14183,7 +14239,7 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
14183
14239
|
);
|
|
14184
14240
|
const message = error instanceof Error ? error.message : String(error);
|
|
14185
14241
|
return {
|
|
14186
|
-
text:
|
|
14242
|
+
text: "",
|
|
14187
14243
|
...getSandboxMetadata(),
|
|
14188
14244
|
diagnostics: {
|
|
14189
14245
|
outcome: "provider_error",
|
|
@@ -14574,6 +14630,98 @@ function getConversationMessageSlackTs(message) {
|
|
|
14574
14630
|
return message.meta?.slackTs ?? toOptionalString(message.id);
|
|
14575
14631
|
}
|
|
14576
14632
|
|
|
14633
|
+
// src/chat/services/turn-failure-response.ts
|
|
14634
|
+
function requireTurnFailureEventId(eventId, eventName) {
|
|
14635
|
+
if (!eventId) {
|
|
14636
|
+
throw new Error(`Sentry did not return an event ID for ${eventName}`);
|
|
14637
|
+
}
|
|
14638
|
+
return eventId;
|
|
14639
|
+
}
|
|
14640
|
+
function getExecutionFailureReason(reply) {
|
|
14641
|
+
const errorMessage = reply.diagnostics.errorMessage?.trim();
|
|
14642
|
+
if (errorMessage) {
|
|
14643
|
+
return errorMessage;
|
|
14644
|
+
}
|
|
14645
|
+
if (reply.diagnostics.toolErrorCount > 0) {
|
|
14646
|
+
return `${reply.diagnostics.toolErrorCount} tool result error(s)`;
|
|
14647
|
+
}
|
|
14648
|
+
if (reply.diagnostics.assistantMessageCount > 0) {
|
|
14649
|
+
return "assistant returned no text";
|
|
14650
|
+
}
|
|
14651
|
+
return "empty assistant turn";
|
|
14652
|
+
}
|
|
14653
|
+
function getFailureCapture(reply) {
|
|
14654
|
+
if (reply.diagnostics.outcome === "provider_error") {
|
|
14655
|
+
return {
|
|
14656
|
+
eventName: "agent_turn_provider_error",
|
|
14657
|
+
error: reply.diagnostics.providerError ?? new Error(
|
|
14658
|
+
reply.diagnostics.errorMessage ?? "Provider error without explicit message"
|
|
14659
|
+
),
|
|
14660
|
+
attributes: {},
|
|
14661
|
+
body: "Agent turn failed with provider error"
|
|
14662
|
+
};
|
|
14663
|
+
}
|
|
14664
|
+
const failureReason = getExecutionFailureReason(reply);
|
|
14665
|
+
return {
|
|
14666
|
+
eventName: "agent_turn_execution_failure",
|
|
14667
|
+
error: new Error(`Agent turn execution failure: ${failureReason}`),
|
|
14668
|
+
attributes: {
|
|
14669
|
+
"app.ai.execution_failure_reason": failureReason
|
|
14670
|
+
},
|
|
14671
|
+
body: "Agent turn completed with execution failure"
|
|
14672
|
+
};
|
|
14673
|
+
}
|
|
14674
|
+
function getAgentTurnDiagnosticsAttributes(reply) {
|
|
14675
|
+
return {
|
|
14676
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
14677
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
14678
|
+
"app.ai.outcome": reply.diagnostics.outcome,
|
|
14679
|
+
"app.ai.assistant_messages": reply.diagnostics.assistantMessageCount,
|
|
14680
|
+
"app.ai.tool_results": reply.diagnostics.toolResultCount,
|
|
14681
|
+
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
|
|
14682
|
+
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
14683
|
+
"app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
|
|
14684
|
+
...reply.diagnostics.thinkingLevel ? {
|
|
14685
|
+
"app.ai.reasoning_effort": reply.diagnostics.thinkingLevel
|
|
14686
|
+
} : {},
|
|
14687
|
+
...reply.diagnostics.stopReason ? {
|
|
14688
|
+
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
14689
|
+
} : {},
|
|
14690
|
+
...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
|
|
14691
|
+
};
|
|
14692
|
+
}
|
|
14693
|
+
function finalizeFailedTurnReply(args) {
|
|
14694
|
+
if (args.reply.diagnostics.outcome === "success") {
|
|
14695
|
+
return args.reply;
|
|
14696
|
+
}
|
|
14697
|
+
const capture = getFailureCapture(args.reply);
|
|
14698
|
+
const eventId = requireTurnFailureEventId(
|
|
14699
|
+
args.logException(
|
|
14700
|
+
capture.error,
|
|
14701
|
+
capture.eventName,
|
|
14702
|
+
args.context,
|
|
14703
|
+
{
|
|
14704
|
+
...getAgentTurnDiagnosticsAttributes(args.reply),
|
|
14705
|
+
...args.attributes,
|
|
14706
|
+
...capture.attributes
|
|
14707
|
+
},
|
|
14708
|
+
capture.body
|
|
14709
|
+
),
|
|
14710
|
+
capture.eventName
|
|
14711
|
+
);
|
|
14712
|
+
const providerPartialText = args.reply.diagnostics.outcome === "provider_error" && args.reply.diagnostics.assistantMessageCount > 0 ? args.reply.text.trim() : "";
|
|
14713
|
+
return {
|
|
14714
|
+
...args.reply,
|
|
14715
|
+
text: providerPartialText ? `${providerPartialText}${getInterruptionMarker()}` : buildTurnFailureResponse(eventId),
|
|
14716
|
+
deliveryMode: "thread",
|
|
14717
|
+
deliveryPlan: {
|
|
14718
|
+
mode: "thread",
|
|
14719
|
+
postThreadText: true,
|
|
14720
|
+
attachFiles: args.reply.files && args.reply.files.length > 0 ? "inline" : "none"
|
|
14721
|
+
}
|
|
14722
|
+
};
|
|
14723
|
+
}
|
|
14724
|
+
|
|
14577
14725
|
// src/chat/plugins/task-runner.ts
|
|
14578
14726
|
import { pluginRunContextSchema } from "@sentry/junior-plugin-api";
|
|
14579
14727
|
var PLUGIN_TASK_LOCK_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -14849,13 +14997,19 @@ export {
|
|
|
14849
14997
|
isCooperativeTurnYieldError,
|
|
14850
14998
|
TurnInputCommitLostError,
|
|
14851
14999
|
isTurnInputCommitLostError,
|
|
15000
|
+
TurnInputDeferredError,
|
|
15001
|
+
isTurnInputDeferredError,
|
|
14852
15002
|
startActiveTurn,
|
|
14853
15003
|
markTurnClosed,
|
|
14854
15004
|
markTurnFailed,
|
|
14855
15005
|
isRedundantReactionAckText,
|
|
15006
|
+
persistCompletedSessionRecord,
|
|
15007
|
+
completeDeliveredTurn,
|
|
15008
|
+
persistYieldSessionRecord,
|
|
14856
15009
|
finalizeMcpAuthorization,
|
|
14857
15010
|
resolvePluginOAuthAccount,
|
|
14858
15011
|
executeCredentialedEgressRequest,
|
|
15012
|
+
buildSteeringPiMessage,
|
|
14859
15013
|
generateAssistantReply,
|
|
14860
15014
|
estimateTextTokens,
|
|
14861
15015
|
getAgentContextCompactionTriggerTokens,
|
|
@@ -14869,6 +15023,9 @@ export {
|
|
|
14869
15023
|
createConversationMemoryService,
|
|
14870
15024
|
isHumanConversationMessage,
|
|
14871
15025
|
getConversationMessageSlackTs,
|
|
15026
|
+
requireTurnFailureEventId,
|
|
15027
|
+
getAgentTurnDiagnosticsAttributes,
|
|
15028
|
+
finalizeFailedTurnReply,
|
|
14872
15029
|
scheduleSessionCompletedPluginTasks,
|
|
14873
15030
|
processPluginTask,
|
|
14874
15031
|
buildDeliveredTurnStatePatch
|