@sentry/junior 0.85.0 → 0.87.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 +779 -356
- 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/no-reply.d.ts +7 -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/reply-delivery-plan.d.ts +0 -4
- 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-QTTTRBNB.js → chunk-6I6HBOQM.js} +80 -15
- package/dist/{chunk-ENEWQMRJ.js → chunk-6O5UI3RG.js} +1 -1
- package/dist/{chunk-GY7L6VCT.js → chunk-ACJJJEGG.js} +35 -17
- package/dist/{chunk-MHEV3T2Y.js → chunk-B6FQPS7A.js} +953 -795
- package/dist/{chunk-MTE5NRPJ.js → chunk-BRSQQRG6.js} +1 -1
- package/dist/{chunk-EENOFKDN.js → chunk-ENPSU7L7.js} +165 -27
- 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-DMP3IQNM.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,
|
|
@@ -22,15 +22,19 @@ import {
|
|
|
22
22
|
buildPluginSystemPromptContributions,
|
|
23
23
|
buildSystemPrompt,
|
|
24
24
|
buildTurnContextPrompt,
|
|
25
|
+
containsNoReplyMarker,
|
|
25
26
|
getAgentTurnSessionRecord,
|
|
27
|
+
getInterruptionMarker,
|
|
28
|
+
isNoReplyMarker,
|
|
26
29
|
loadConnectedMcpProviders,
|
|
27
30
|
recordAuthorizationRequested,
|
|
28
31
|
recordMcpProviderConnected,
|
|
29
32
|
recordSubagentEnded,
|
|
30
33
|
recordSubagentStarted,
|
|
31
34
|
recordToolExecutionStarted,
|
|
35
|
+
stripNoReplyMarker,
|
|
32
36
|
upsertAgentTurnSessionRecord
|
|
33
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-ACJJJEGG.js";
|
|
34
38
|
import {
|
|
35
39
|
createPluginEmbedder,
|
|
36
40
|
createPluginHookRunner,
|
|
@@ -41,14 +45,15 @@ import {
|
|
|
41
45
|
getPlugins,
|
|
42
46
|
getSlackToolContext,
|
|
43
47
|
resolveChannelCapabilities
|
|
44
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-2NFV5FMB.js";
|
|
45
49
|
import {
|
|
46
50
|
createPluginLogger,
|
|
47
51
|
createPluginState
|
|
48
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-BRSQQRG6.js";
|
|
49
53
|
import {
|
|
54
|
+
getConversationStore,
|
|
50
55
|
getDb
|
|
51
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-6I6HBOQM.js";
|
|
52
57
|
import {
|
|
53
58
|
SANDBOX_DATA_ROOT,
|
|
54
59
|
SANDBOX_SKILLS_ROOT,
|
|
@@ -59,7 +64,7 @@ import {
|
|
|
59
64
|
import {
|
|
60
65
|
acquireActiveLock,
|
|
61
66
|
getStateAdapter
|
|
62
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-GB5DFM4D.js";
|
|
63
68
|
import {
|
|
64
69
|
SlackActionError,
|
|
65
70
|
downloadPrivateSlackFile,
|
|
@@ -70,7 +75,7 @@ import {
|
|
|
70
75
|
normalizeSlackConversationId,
|
|
71
76
|
parseDestination,
|
|
72
77
|
withSlackRetries
|
|
73
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-GGD6WK6V.js";
|
|
74
79
|
import {
|
|
75
80
|
GEN_AI_PROVIDER_NAME,
|
|
76
81
|
GEN_AI_SERVER_ADDRESS,
|
|
@@ -106,7 +111,7 @@ import {
|
|
|
106
111
|
toObservablePromptPart,
|
|
107
112
|
trimTrailingAssistantMessages,
|
|
108
113
|
upsertActiveSkill
|
|
109
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-ZU2ALUVQ.js";
|
|
110
115
|
import {
|
|
111
116
|
parseSlackThreadId,
|
|
112
117
|
resolveConversationPrivacy,
|
|
@@ -116,7 +121,7 @@ import {
|
|
|
116
121
|
toGenAiPayloadMetadata,
|
|
117
122
|
toGenAiPayloadTraceAttributes,
|
|
118
123
|
toGenAiTextMetadata
|
|
119
|
-
} from "./chunk-
|
|
124
|
+
} from "./chunk-L7OHKDOX.js";
|
|
120
125
|
import {
|
|
121
126
|
discoverSkills,
|
|
122
127
|
findSkillByName,
|
|
@@ -138,6 +143,7 @@ import {
|
|
|
138
143
|
listReferenceFiles
|
|
139
144
|
} from "./chunk-6APU57E6.js";
|
|
140
145
|
import {
|
|
146
|
+
buildTurnFailureResponse,
|
|
141
147
|
extractGenAiUsageAttributes,
|
|
142
148
|
extractGenAiUsageSummary,
|
|
143
149
|
getActiveTraceId,
|
|
@@ -650,6 +656,16 @@ var TurnInputCommitLostError = class extends Error {
|
|
|
650
656
|
function isTurnInputCommitLostError(error) {
|
|
651
657
|
return error instanceof TurnInputCommitLostError;
|
|
652
658
|
}
|
|
659
|
+
var TurnInputDeferredError = class extends Error {
|
|
660
|
+
code = "turn_input_deferred";
|
|
661
|
+
constructor(message = "Turn input is deferred until the active resume ends") {
|
|
662
|
+
super(message);
|
|
663
|
+
this.name = "TurnInputDeferredError";
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
function isTurnInputDeferredError(error) {
|
|
667
|
+
return error instanceof TurnInputDeferredError;
|
|
668
|
+
}
|
|
653
669
|
function startActiveTurn(args) {
|
|
654
670
|
args.conversation.processing.activeTurnId = args.nextTurnId;
|
|
655
671
|
args.updateConversationStats(args.conversation);
|
|
@@ -679,186 +695,593 @@ function markTurnFailed(args) {
|
|
|
679
695
|
args.updateConversationStats(args.conversation);
|
|
680
696
|
}
|
|
681
697
|
|
|
682
|
-
// src/chat/
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
function
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
698
|
+
// src/chat/usage.ts
|
|
699
|
+
var COMPONENT_USAGE_FIELDS = [
|
|
700
|
+
"inputTokens",
|
|
701
|
+
"outputTokens",
|
|
702
|
+
"cachedInputTokens",
|
|
703
|
+
"cacheCreationTokens"
|
|
704
|
+
];
|
|
705
|
+
function hasAgentTurnUsage(usage) {
|
|
706
|
+
return Boolean(
|
|
707
|
+
usage && Object.values(usage).some(
|
|
708
|
+
(value) => typeof value === "number" && Number.isFinite(value)
|
|
709
|
+
)
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
function getFiniteCount(value) {
|
|
713
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
714
|
+
}
|
|
715
|
+
function getComponentTotal(usage) {
|
|
716
|
+
let total;
|
|
717
|
+
for (const field of COMPONENT_USAGE_FIELDS) {
|
|
718
|
+
const value = getFiniteCount(usage[field]);
|
|
719
|
+
if (value === void 0) continue;
|
|
720
|
+
total = (total ?? 0) + value;
|
|
721
|
+
}
|
|
722
|
+
return total;
|
|
723
|
+
}
|
|
724
|
+
function addAgentTurnUsage(...usages) {
|
|
725
|
+
const components = {};
|
|
726
|
+
let componentTotal;
|
|
727
|
+
let totalOnlyTokens;
|
|
728
|
+
for (const usage of usages) {
|
|
729
|
+
if (!usage) continue;
|
|
730
|
+
const usageComponentTotal = getComponentTotal(usage);
|
|
731
|
+
if (usageComponentTotal !== void 0) {
|
|
732
|
+
componentTotal = (componentTotal ?? 0) + usageComponentTotal;
|
|
733
|
+
for (const field of COMPONENT_USAGE_FIELDS) {
|
|
734
|
+
const value = getFiniteCount(usage[field]);
|
|
735
|
+
if (value === void 0) continue;
|
|
736
|
+
components[field] = (components[field] ?? 0) + value;
|
|
700
737
|
}
|
|
701
|
-
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
const totalTokens = getFiniteCount(usage.totalTokens);
|
|
741
|
+
if (totalTokens !== void 0) {
|
|
742
|
+
totalOnlyTokens = (totalOnlyTokens ?? 0) + totalTokens;
|
|
702
743
|
}
|
|
703
744
|
}
|
|
704
|
-
|
|
705
|
-
|
|
745
|
+
if (totalOnlyTokens !== void 0) {
|
|
746
|
+
return {
|
|
747
|
+
totalTokens: totalOnlyTokens + (componentTotal ?? 0)
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
return hasAgentTurnUsage(components) ? components : void 0;
|
|
706
751
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
...new Set(providers.flatMap((p) => p.configKeys))
|
|
715
|
-
].sort();
|
|
716
|
-
logInfo(
|
|
717
|
-
"capability_catalog_loaded",
|
|
718
|
-
{},
|
|
752
|
+
|
|
753
|
+
// src/chat/services/turn-session-record.ts
|
|
754
|
+
var AGENT_CONTINUE_MAX_SLICES = 48;
|
|
755
|
+
function logSessionRecordError(error, eventName, args, attributes, message) {
|
|
756
|
+
logException(
|
|
757
|
+
error,
|
|
758
|
+
eventName,
|
|
719
759
|
{
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
760
|
+
slackThreadId: args.logContext.threadId,
|
|
761
|
+
slackUserId: args.logContext.requesterId,
|
|
762
|
+
slackChannelId: args.logContext.channelId,
|
|
763
|
+
runId: args.logContext.runId,
|
|
764
|
+
assistantUserName: args.logContext.assistantUserName,
|
|
765
|
+
modelId: args.logContext.modelId
|
|
725
766
|
},
|
|
726
|
-
|
|
767
|
+
{
|
|
768
|
+
"app.ai.resume_conversation_id": args.conversationId,
|
|
769
|
+
"app.ai.resume_session_id": args.sessionId,
|
|
770
|
+
...attributes
|
|
771
|
+
},
|
|
772
|
+
message
|
|
727
773
|
);
|
|
728
774
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
775
|
+
function addDurationMs(prior, current) {
|
|
776
|
+
const total = [prior, current].reduce((sum, value) => {
|
|
777
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
778
|
+
return sum;
|
|
779
|
+
}
|
|
780
|
+
return (sum ?? 0) + Math.max(0, Math.floor(value));
|
|
781
|
+
}, void 0);
|
|
782
|
+
return total;
|
|
783
|
+
}
|
|
784
|
+
function isContinuableBoundary(messages) {
|
|
785
|
+
const lastRole = getPiMessageRole(messages.at(-1));
|
|
786
|
+
return lastRole === "user" || lastRole === "toolResult";
|
|
787
|
+
}
|
|
788
|
+
function resumableBoundary(messages, fallbackMessages) {
|
|
789
|
+
const current = trimTrailingAssistantMessages(messages);
|
|
790
|
+
if (current.length > 0 && isContinuableBoundary(current)) {
|
|
791
|
+
return current;
|
|
735
792
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
793
|
+
return trimTrailingAssistantMessages(fallbackMessages ?? []);
|
|
794
|
+
}
|
|
795
|
+
async function loadTurnSessionRecord(ctx) {
|
|
796
|
+
const canUseTurnSession = Boolean(ctx.conversationId && ctx.sessionId);
|
|
797
|
+
const existingSessionRecord = canUseTurnSession && ctx.conversationId && ctx.sessionId ? await getAgentTurnSessionRecord(ctx.conversationId, ctx.sessionId) : void 0;
|
|
798
|
+
const hasAwaitingResumeRecord = Boolean(
|
|
799
|
+
existingSessionRecord && existingSessionRecord.state === "awaiting_resume"
|
|
800
|
+
);
|
|
801
|
+
return {
|
|
802
|
+
canUseTurnSession,
|
|
803
|
+
resumedFromSessionRecord: hasAwaitingResumeRecord,
|
|
804
|
+
currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
|
|
805
|
+
existingSessionRecord
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
async function persistRunningSessionRecord(args) {
|
|
809
|
+
if (args.messages.length === 0 || !isContinuableBoundary(args.messages)) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
try {
|
|
813
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
814
|
+
args.conversationId,
|
|
815
|
+
args.sessionId
|
|
816
|
+
);
|
|
817
|
+
await upsertAgentTurnSessionRecord({
|
|
818
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
819
|
+
conversationId: args.conversationId,
|
|
820
|
+
cumulativeDurationMs: latestSessionRecord?.cumulativeDurationMs,
|
|
821
|
+
cumulativeUsage: latestSessionRecord?.cumulativeUsage,
|
|
822
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
823
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
824
|
+
sessionId: args.sessionId,
|
|
825
|
+
sliceId: args.sliceId,
|
|
826
|
+
state: "running",
|
|
827
|
+
piMessages: args.messages,
|
|
828
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
829
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
830
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
831
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
832
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
833
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
834
|
+
} : {}
|
|
835
|
+
});
|
|
836
|
+
return true;
|
|
837
|
+
} catch (recordError) {
|
|
838
|
+
logSessionRecordError(
|
|
839
|
+
recordError,
|
|
840
|
+
"agent_turn_running_session_record_failed",
|
|
841
|
+
args,
|
|
842
|
+
{
|
|
843
|
+
"app.ai.resume_slice_id": args.sliceId
|
|
844
|
+
},
|
|
845
|
+
"Failed to persist running turn session record"
|
|
846
|
+
);
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
async function persistCompletedSessionRecord(args) {
|
|
851
|
+
let sliceId = args.sliceId;
|
|
852
|
+
try {
|
|
853
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
854
|
+
args.conversationId,
|
|
855
|
+
args.sessionId
|
|
856
|
+
);
|
|
857
|
+
sliceId = sliceId ?? latestSessionRecord?.sliceId;
|
|
858
|
+
if (sliceId === void 0) {
|
|
739
859
|
throw new Error(
|
|
740
|
-
|
|
860
|
+
"Completed session record requires a slice id from the caller or the latest stored record"
|
|
741
861
|
);
|
|
742
862
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
863
|
+
await upsertAgentTurnSessionRecord({
|
|
864
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
865
|
+
conversationId: args.conversationId,
|
|
866
|
+
cumulativeDurationMs: addDurationMs(
|
|
867
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
868
|
+
args.currentDurationMs
|
|
869
|
+
),
|
|
870
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
871
|
+
latestSessionRecord?.cumulativeUsage,
|
|
872
|
+
args.currentUsage
|
|
873
|
+
),
|
|
874
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
875
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
876
|
+
...args.destinationVisibility ? { destinationVisibility: args.destinationVisibility } : {},
|
|
877
|
+
sessionId: args.sessionId,
|
|
878
|
+
sliceId,
|
|
879
|
+
state: "completed",
|
|
880
|
+
piMessages: args.allMessages,
|
|
881
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
882
|
+
...args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames ? {
|
|
883
|
+
loadedSkillNames: args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames
|
|
884
|
+
} : {},
|
|
885
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
886
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
887
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
888
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
889
|
+
} : {}
|
|
746
890
|
});
|
|
891
|
+
} catch (recordError) {
|
|
892
|
+
logSessionRecordError(
|
|
893
|
+
recordError,
|
|
894
|
+
"agent_turn_completed_session_record_failed",
|
|
895
|
+
args,
|
|
896
|
+
sliceId !== void 0 ? { "app.ai.resume_slice_id": sliceId } : {},
|
|
897
|
+
"Failed to persist completed turn session record"
|
|
898
|
+
);
|
|
747
899
|
}
|
|
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
900
|
}
|
|
765
|
-
function
|
|
766
|
-
|
|
901
|
+
async function completeDeliveredTurn(args) {
|
|
902
|
+
await persistCompletedSessionRecord({
|
|
903
|
+
channelName: args.channelName,
|
|
904
|
+
conversationId: args.conversationId,
|
|
905
|
+
currentDurationMs: args.durationMs,
|
|
906
|
+
currentUsage: args.usage,
|
|
907
|
+
destination: args.destination,
|
|
908
|
+
destinationVisibility: args.destinationVisibility,
|
|
909
|
+
source: args.source,
|
|
910
|
+
sessionId: args.sessionId,
|
|
911
|
+
sliceId: args.sliceId,
|
|
912
|
+
allMessages: args.messages,
|
|
913
|
+
loadedSkillNames: args.loadedSkillNames,
|
|
914
|
+
logContext: args.logContext,
|
|
915
|
+
requester: args.requester,
|
|
916
|
+
surface: args.surface,
|
|
917
|
+
turnStartMessageIndex: args.turnStartMessageIndex
|
|
918
|
+
});
|
|
767
919
|
}
|
|
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);
|
|
920
|
+
async function persistAuthPauseSessionRecord(args) {
|
|
921
|
+
const nextSliceId = args.currentSliceId + 1;
|
|
922
|
+
try {
|
|
923
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
924
|
+
args.conversationId,
|
|
925
|
+
args.sessionId
|
|
926
|
+
);
|
|
927
|
+
const piMessages = resumableBoundary(
|
|
928
|
+
args.messages,
|
|
929
|
+
latestSessionRecord?.piMessages
|
|
930
|
+
);
|
|
931
|
+
if (piMessages.length > 0 && !isContinuableBoundary(piMessages)) {
|
|
932
|
+
return void 0;
|
|
806
933
|
}
|
|
934
|
+
return await upsertAgentTurnSessionRecord({
|
|
935
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
936
|
+
conversationId: args.conversationId,
|
|
937
|
+
cumulativeDurationMs: addDurationMs(
|
|
938
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
939
|
+
args.currentDurationMs
|
|
940
|
+
),
|
|
941
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
942
|
+
latestSessionRecord?.cumulativeUsage,
|
|
943
|
+
args.currentUsage
|
|
944
|
+
),
|
|
945
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
946
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
947
|
+
sessionId: args.sessionId,
|
|
948
|
+
sliceId: nextSliceId,
|
|
949
|
+
state: "awaiting_resume",
|
|
950
|
+
piMessages,
|
|
951
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
952
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
953
|
+
resumeReason: "auth",
|
|
954
|
+
resumedFromSliceId: args.currentSliceId,
|
|
955
|
+
errorMessage: args.errorMessage,
|
|
956
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
957
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
958
|
+
});
|
|
959
|
+
} catch (recordError) {
|
|
960
|
+
logSessionRecordError(
|
|
961
|
+
recordError,
|
|
962
|
+
"agent_turn_auth_resume_session_record_failed",
|
|
963
|
+
args,
|
|
964
|
+
{
|
|
965
|
+
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
966
|
+
"app.ai.resume_next_slice_id": nextSliceId
|
|
967
|
+
},
|
|
968
|
+
"Failed to persist auth session record before retry"
|
|
969
|
+
);
|
|
807
970
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
// src/chat/capabilities/factory.ts
|
|
811
|
-
var sandboxEgressRouters = /* @__PURE__ */ new WeakMap();
|
|
812
|
-
function createUserTokenStore() {
|
|
813
|
-
return new StateAdapterTokenStore(getStateAdapter());
|
|
971
|
+
return void 0;
|
|
814
972
|
}
|
|
815
|
-
function
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
973
|
+
async function persistTimeoutSessionRecord(args) {
|
|
974
|
+
const nextSliceId = args.currentSliceId + 1;
|
|
975
|
+
try {
|
|
976
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
977
|
+
args.conversationId,
|
|
978
|
+
args.sessionId
|
|
979
|
+
);
|
|
980
|
+
const piMessages = resumableBoundary(
|
|
981
|
+
args.messages,
|
|
982
|
+
latestSessionRecord?.piMessages
|
|
983
|
+
);
|
|
984
|
+
if (piMessages.length === 0 || !isContinuableBoundary(piMessages)) {
|
|
985
|
+
return void 0;
|
|
822
986
|
}
|
|
823
|
-
|
|
824
|
-
|
|
987
|
+
const cumulativeDurationMs = addDurationMs(
|
|
988
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
989
|
+
args.currentDurationMs
|
|
990
|
+
);
|
|
991
|
+
const cumulativeUsage = addAgentTurnUsage(
|
|
992
|
+
latestSessionRecord?.cumulativeUsage,
|
|
993
|
+
args.currentUsage
|
|
994
|
+
);
|
|
995
|
+
if (nextSliceId > AGENT_CONTINUE_MAX_SLICES) {
|
|
996
|
+
return await upsertAgentTurnSessionRecord({
|
|
997
|
+
...args.channelName ?? latestSessionRecord?.channelName ? {
|
|
998
|
+
channelName: args.channelName ?? latestSessionRecord?.channelName
|
|
999
|
+
} : {},
|
|
1000
|
+
conversationId: args.conversationId,
|
|
1001
|
+
cumulativeDurationMs,
|
|
1002
|
+
cumulativeUsage,
|
|
1003
|
+
...args.destination ?? latestSessionRecord?.destination ? {
|
|
1004
|
+
destination: args.destination ?? latestSessionRecord?.destination
|
|
1005
|
+
} : {},
|
|
1006
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1007
|
+
sessionId: args.sessionId,
|
|
1008
|
+
sliceId: args.currentSliceId,
|
|
1009
|
+
state: "failed",
|
|
1010
|
+
piMessages,
|
|
1011
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1012
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1013
|
+
resumeReason: "timeout",
|
|
1014
|
+
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
1015
|
+
errorMessage: `Agent continuation exceeded slice limit (${AGENT_CONTINUE_MAX_SLICES})`,
|
|
1016
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
1017
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
return await upsertAgentTurnSessionRecord({
|
|
1021
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1022
|
+
conversationId: args.conversationId,
|
|
1023
|
+
cumulativeDurationMs,
|
|
1024
|
+
cumulativeUsage,
|
|
1025
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1026
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1027
|
+
sessionId: args.sessionId,
|
|
1028
|
+
sliceId: nextSliceId,
|
|
1029
|
+
state: "awaiting_resume",
|
|
1030
|
+
piMessages,
|
|
1031
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1032
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1033
|
+
resumeReason: "timeout",
|
|
1034
|
+
resumedFromSliceId: args.currentSliceId,
|
|
1035
|
+
errorMessage: args.errorMessage,
|
|
1036
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
1037
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
825
1038
|
});
|
|
1039
|
+
} catch (recordError) {
|
|
1040
|
+
logSessionRecordError(
|
|
1041
|
+
recordError,
|
|
1042
|
+
"agent_continue_session_record_failed",
|
|
1043
|
+
args,
|
|
1044
|
+
{
|
|
1045
|
+
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
1046
|
+
"app.ai.resume_next_slice_id": nextSliceId
|
|
1047
|
+
},
|
|
1048
|
+
"Failed to persist session record before scheduling agent continuation"
|
|
1049
|
+
);
|
|
1050
|
+
return void 0;
|
|
826
1051
|
}
|
|
827
|
-
return new ProviderCredentialRouter({ brokersByProvider });
|
|
828
1052
|
}
|
|
829
|
-
function
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
new StateAdapterTokenStore(stateAdapter)
|
|
1053
|
+
async function persistYieldSessionRecord(args) {
|
|
1054
|
+
try {
|
|
1055
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
1056
|
+
args.conversationId,
|
|
1057
|
+
args.sessionId
|
|
835
1058
|
);
|
|
836
|
-
|
|
1059
|
+
const piMessages = resumableBoundary(
|
|
1060
|
+
args.messages,
|
|
1061
|
+
latestSessionRecord?.piMessages
|
|
1062
|
+
);
|
|
1063
|
+
if (piMessages.length === 0 || !isContinuableBoundary(piMessages)) {
|
|
1064
|
+
return void 0;
|
|
1065
|
+
}
|
|
1066
|
+
return await upsertAgentTurnSessionRecord({
|
|
1067
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1068
|
+
conversationId: args.conversationId,
|
|
1069
|
+
cumulativeDurationMs: addDurationMs(
|
|
1070
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
1071
|
+
args.currentDurationMs
|
|
1072
|
+
),
|
|
1073
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
1074
|
+
latestSessionRecord?.cumulativeUsage,
|
|
1075
|
+
args.currentUsage
|
|
1076
|
+
),
|
|
1077
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1078
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1079
|
+
sessionId: args.sessionId,
|
|
1080
|
+
sliceId: args.currentSliceId,
|
|
1081
|
+
state: "awaiting_resume",
|
|
1082
|
+
piMessages,
|
|
1083
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1084
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1085
|
+
resumeReason: "yield",
|
|
1086
|
+
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
1087
|
+
errorMessage: args.errorMessage,
|
|
1088
|
+
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
1089
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1090
|
+
});
|
|
1091
|
+
} catch (recordError) {
|
|
1092
|
+
logSessionRecordError(
|
|
1093
|
+
recordError,
|
|
1094
|
+
"agent_turn_yield_session_record_failed",
|
|
1095
|
+
args,
|
|
1096
|
+
{
|
|
1097
|
+
"app.ai.resume_slice_id": args.currentSliceId
|
|
1098
|
+
},
|
|
1099
|
+
"Failed to persist cooperative yield session record"
|
|
1100
|
+
);
|
|
1101
|
+
return void 0;
|
|
837
1102
|
}
|
|
838
|
-
return router;
|
|
839
|
-
}
|
|
840
|
-
async function issueProviderCredentialLease(input) {
|
|
841
|
-
return await getSandboxEgressRouter().issue(input);
|
|
842
1103
|
}
|
|
843
1104
|
|
|
844
|
-
// src/chat/
|
|
845
|
-
import {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1105
|
+
// src/chat/respond.ts
|
|
1106
|
+
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
1107
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
1108
|
+
import { z as z4 } from "zod";
|
|
1109
|
+
|
|
1110
|
+
// src/chat/capabilities/catalog.ts
|
|
1111
|
+
var cachedCatalog;
|
|
1112
|
+
function getCapabilityCatalog() {
|
|
1113
|
+
const signature = pluginCatalogRuntime.getSignature();
|
|
1114
|
+
if (cachedCatalog?.signature === signature) return cachedCatalog;
|
|
1115
|
+
const providers = pluginCatalogRuntime.getCapabilityProviders();
|
|
1116
|
+
const capabilityToProvider = /* @__PURE__ */ new Map();
|
|
1117
|
+
for (const provider of providers) {
|
|
1118
|
+
for (const capability of provider.capabilities) {
|
|
1119
|
+
if (capabilityToProvider.has(capability)) {
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
`Duplicate capability registration for "${capability}"`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
capabilityToProvider.set(capability, provider);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
cachedCatalog = { signature, providers, capabilityToProvider };
|
|
1128
|
+
return cachedCatalog;
|
|
1129
|
+
}
|
|
1130
|
+
var catalogLogged = false;
|
|
1131
|
+
function logCapabilityCatalogLoadedOnce() {
|
|
1132
|
+
if (catalogLogged) return;
|
|
1133
|
+
catalogLogged = true;
|
|
1134
|
+
const { providers } = getCapabilityCatalog();
|
|
1135
|
+
const capabilityNames = providers.flatMap((p) => p.capabilities).sort();
|
|
1136
|
+
const configKeys = [
|
|
1137
|
+
...new Set(providers.flatMap((p) => p.configKeys))
|
|
1138
|
+
].sort();
|
|
1139
|
+
logInfo(
|
|
1140
|
+
"capability_catalog_loaded",
|
|
1141
|
+
{},
|
|
1142
|
+
{
|
|
1143
|
+
"app.capability.providers": providers.map((p) => p.provider),
|
|
1144
|
+
"app.capability.count": capabilityNames.length,
|
|
1145
|
+
"app.capability.names": capabilityNames,
|
|
1146
|
+
"app.config.key_count": configKeys.length,
|
|
1147
|
+
"app.config.keys": configKeys
|
|
1148
|
+
},
|
|
1149
|
+
"Loaded capability provider catalog"
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/chat/capabilities/router.ts
|
|
1154
|
+
var ProviderCredentialRouter = class {
|
|
1155
|
+
brokersByProvider;
|
|
1156
|
+
constructor(input) {
|
|
1157
|
+
this.brokersByProvider = input.brokersByProvider;
|
|
1158
|
+
}
|
|
1159
|
+
async issue(input) {
|
|
1160
|
+
const broker = this.brokersByProvider[input.provider];
|
|
1161
|
+
if (!broker) {
|
|
1162
|
+
throw new Error(
|
|
1163
|
+
`No credential broker registered for provider: ${input.provider}`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
return await broker.issue({
|
|
1167
|
+
context: input.context,
|
|
1168
|
+
reason: input.reason
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
// src/chat/credentials/user-token-store.ts
|
|
1174
|
+
import {
|
|
1175
|
+
pluginStoredTokensSchema
|
|
1176
|
+
} from "@sentry/junior-plugin-api";
|
|
1177
|
+
var storedTokensSchema = pluginStoredTokensSchema;
|
|
1178
|
+
|
|
1179
|
+
// src/chat/credentials/state-adapter-token-store.ts
|
|
1180
|
+
var KEY_PREFIX = "oauth-token";
|
|
1181
|
+
var BUFFER_MS = 24 * 60 * 60 * 1e3;
|
|
1182
|
+
var LONG_LIVED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
1183
|
+
var REFRESH_LOCK_WAIT_MS = 3e4;
|
|
1184
|
+
var REFRESH_LOCK_RETRY_MS = 100;
|
|
1185
|
+
function tokenKey(userId, provider) {
|
|
1186
|
+
return `${KEY_PREFIX}:${userId}:${provider}`;
|
|
1187
|
+
}
|
|
1188
|
+
function refreshLockKey(userId, provider) {
|
|
1189
|
+
return `${tokenKey(userId, provider)}:refresh`;
|
|
1190
|
+
}
|
|
1191
|
+
async function sleep(ms) {
|
|
1192
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1193
|
+
}
|
|
1194
|
+
var StateAdapterTokenStore = class {
|
|
1195
|
+
state;
|
|
1196
|
+
constructor(stateAdapter) {
|
|
1197
|
+
this.state = stateAdapter;
|
|
1198
|
+
}
|
|
1199
|
+
async get(userId, provider) {
|
|
1200
|
+
const stored = await this.state.get(tokenKey(userId, provider));
|
|
1201
|
+
return stored === null || stored === void 0 ? void 0 : storedTokensSchema.parse(stored);
|
|
1202
|
+
}
|
|
1203
|
+
async set(userId, provider, tokens) {
|
|
1204
|
+
const parsed = storedTokensSchema.parse(tokens);
|
|
1205
|
+
const expiresAt = parsed.refreshTokenExpiresAt ?? parsed.expiresAt;
|
|
1206
|
+
const ttlMs2 = expiresAt ? Math.max(expiresAt - Date.now() + BUFFER_MS, BUFFER_MS) : LONG_LIVED_TTL_MS;
|
|
1207
|
+
await this.state.set(tokenKey(userId, provider), parsed, ttlMs2);
|
|
1208
|
+
}
|
|
1209
|
+
async delete(userId, provider) {
|
|
1210
|
+
await this.state.delete(tokenKey(userId, provider));
|
|
1211
|
+
}
|
|
1212
|
+
/** Wait for the per-slot refresh gate so rotated refresh tokens are used once. */
|
|
1213
|
+
async withRefresh(userId, provider, callback) {
|
|
1214
|
+
const lockKey = refreshLockKey(userId, provider);
|
|
1215
|
+
const deadline = Date.now() + REFRESH_LOCK_WAIT_MS;
|
|
1216
|
+
while (true) {
|
|
1217
|
+
const lock = await acquireActiveLock(this.state, lockKey);
|
|
1218
|
+
if (lock) {
|
|
1219
|
+
try {
|
|
1220
|
+
return await callback();
|
|
1221
|
+
} finally {
|
|
1222
|
+
await this.state.releaseLock(lock);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
if (Date.now() >= deadline) {
|
|
1226
|
+
throw new Error(`Could not acquire OAuth token refresh lock`);
|
|
1227
|
+
}
|
|
1228
|
+
await sleep(REFRESH_LOCK_RETRY_MS);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
// src/chat/capabilities/factory.ts
|
|
1234
|
+
var sandboxEgressRouters = /* @__PURE__ */ new WeakMap();
|
|
1235
|
+
function createUserTokenStore() {
|
|
1236
|
+
return new StateAdapterTokenStore(getStateAdapter());
|
|
1237
|
+
}
|
|
1238
|
+
function createProviderCredentialRouter(userTokenStore) {
|
|
1239
|
+
logCapabilityCatalogLoadedOnce();
|
|
1240
|
+
const brokersByProvider = {};
|
|
1241
|
+
for (const plugin of pluginCatalogRuntime.getProviders()) {
|
|
1242
|
+
const { name } = plugin.manifest;
|
|
1243
|
+
if (!plugin.manifest.credentials && !plugin.manifest.apiHeaders) {
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
brokersByProvider[name] = pluginCatalogRuntime.createBroker(name, {
|
|
1247
|
+
userTokenStore
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
return new ProviderCredentialRouter({ brokersByProvider });
|
|
1251
|
+
}
|
|
1252
|
+
function getSandboxEgressRouter() {
|
|
1253
|
+
const stateAdapter = getStateAdapter();
|
|
1254
|
+
let router = sandboxEgressRouters.get(stateAdapter);
|
|
1255
|
+
if (!router) {
|
|
1256
|
+
router = createProviderCredentialRouter(
|
|
1257
|
+
new StateAdapterTokenStore(stateAdapter)
|
|
1258
|
+
);
|
|
1259
|
+
sandboxEgressRouters.set(stateAdapter, router);
|
|
1260
|
+
}
|
|
1261
|
+
return router;
|
|
1262
|
+
}
|
|
1263
|
+
async function issueProviderCredentialLease(input) {
|
|
1264
|
+
return await getSandboxEgressRouter().issue(input);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/chat/capabilities/jr-rpc-command.ts
|
|
1268
|
+
import { Bash, defineCommand } from "just-bash";
|
|
1269
|
+
function commandResult(input) {
|
|
1270
|
+
let stdout = "";
|
|
1271
|
+
if (typeof input.stdout === "string") {
|
|
1272
|
+
stdout = input.stdout;
|
|
1273
|
+
} else if (input.stdout !== void 0) {
|
|
1274
|
+
stdout = `${JSON.stringify(input.stdout, null, 2)}
|
|
1275
|
+
`;
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
stdout,
|
|
1279
|
+
stderr: input.stderr ?? "",
|
|
1280
|
+
exitCode: input.exitCode
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
function requireChannelConfiguration(deps) {
|
|
1284
|
+
if (deps.channelConfiguration) {
|
|
862
1285
|
return { ok: true, configuration: deps.channelConfiguration };
|
|
863
1286
|
}
|
|
864
1287
|
return {
|
|
@@ -4470,6 +4893,7 @@ async function getPermalinkBestEffort(args) {
|
|
|
4470
4893
|
3,
|
|
4471
4894
|
{
|
|
4472
4895
|
action: "chat.getPermalink",
|
|
4896
|
+
idempotent: true,
|
|
4473
4897
|
spanAttributes: {
|
|
4474
4898
|
"app.slack.channel_id": args.channelId,
|
|
4475
4899
|
"app.slack.message_ts": args.messageTs
|
|
@@ -4522,30 +4946,6 @@ async function postSlackMessage(input) {
|
|
|
4522
4946
|
} : {}
|
|
4523
4947
|
};
|
|
4524
4948
|
}
|
|
4525
|
-
async function deleteSlackMessage(input) {
|
|
4526
|
-
const channelId = requireSlackConversationId(
|
|
4527
|
-
input.channelId,
|
|
4528
|
-
"Slack message deletion"
|
|
4529
|
-
);
|
|
4530
|
-
const timestamp = requireSlackMessageTimestamp(
|
|
4531
|
-
input.timestamp,
|
|
4532
|
-
"Slack message deletion"
|
|
4533
|
-
);
|
|
4534
|
-
await withSlackRetries(
|
|
4535
|
-
() => getSlackClient().chat.delete({
|
|
4536
|
-
channel: channelId,
|
|
4537
|
-
ts: timestamp
|
|
4538
|
-
}),
|
|
4539
|
-
3,
|
|
4540
|
-
{
|
|
4541
|
-
action: "chat.delete",
|
|
4542
|
-
spanAttributes: {
|
|
4543
|
-
"app.slack.channel_id": channelId,
|
|
4544
|
-
"app.slack.message_ts": timestamp
|
|
4545
|
-
}
|
|
4546
|
-
}
|
|
4547
|
-
);
|
|
4548
|
-
}
|
|
4549
4949
|
async function postSlackEphemeralMessage(input) {
|
|
4550
4950
|
const channelId = requireSlackConversationId(
|
|
4551
4951
|
input.channelId,
|
|
@@ -4647,6 +5047,7 @@ async function addReactionToMessage(input) {
|
|
|
4647
5047
|
3,
|
|
4648
5048
|
{
|
|
4649
5049
|
action: "reactions.add",
|
|
5050
|
+
idempotent: true,
|
|
4650
5051
|
spanAttributes: {
|
|
4651
5052
|
"app.slack.channel_id": channelId,
|
|
4652
5053
|
"app.slack.message_ts": timestamp,
|
|
@@ -4685,6 +5086,7 @@ async function removeReactionFromMessage(input) {
|
|
|
4685
5086
|
3,
|
|
4686
5087
|
{
|
|
4687
5088
|
action: "reactions.remove",
|
|
5089
|
+
idempotent: true,
|
|
4688
5090
|
spanAttributes: {
|
|
4689
5091
|
"app.slack.channel_id": channelId,
|
|
4690
5092
|
"app.slack.message_ts": timestamp,
|
|
@@ -4720,7 +5122,7 @@ function createOperationKey(toolName, input) {
|
|
|
4720
5122
|
// src/chat/tools/slack/channel-post-message.ts
|
|
4721
5123
|
function createSlackChannelPostMessageTool(context, state) {
|
|
4722
5124
|
return tool({
|
|
4723
|
-
description: "Post a new top-level message to the current Slack channel. Use only when the user explicitly asks to post/send/share/say something to the channel. Do not use for thread replies, inline @mentions, or pinging mentioned users.",
|
|
5125
|
+
description: "Post a new top-level message to the current Slack channel. Use only when the user explicitly asks to post/send/share/say something to the current channel. Do not use for other named channels, thread replies, inline @mentions, or pinging mentioned users.",
|
|
4724
5126
|
inputSchema: Type15.Object({
|
|
4725
5127
|
text: Type15.String({
|
|
4726
5128
|
minLength: 1,
|
|
@@ -4765,7 +5167,7 @@ function createSlackChannelPostMessageTool(context, state) {
|
|
|
4765
5167
|
import { Type as Type16 } from "@sinclair/typebox";
|
|
4766
5168
|
function createSlackMessageAddReactionTool(context, state) {
|
|
4767
5169
|
return tool({
|
|
4768
|
-
description: "Add an emoji reaction to the current inbound Slack message. Use
|
|
5170
|
+
description: "Add an emoji reaction to the current inbound Slack message. Use when the user asks for a reaction on the current message without another target. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
|
|
4769
5171
|
inputSchema: Type16.Object({
|
|
4770
5172
|
emoji: Type16.String({
|
|
4771
5173
|
minLength: 1,
|
|
@@ -4777,17 +5179,15 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
4777
5179
|
const targetChannelId = context.sourceChannelId;
|
|
4778
5180
|
const targetMessageTs = context.messageTs;
|
|
4779
5181
|
if (!targetMessageTs) {
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
};
|
|
5182
|
+
throw new ToolInputError(
|
|
5183
|
+
"No active message timestamp is available for reactions."
|
|
5184
|
+
);
|
|
4784
5185
|
}
|
|
4785
5186
|
const normalizedEmoji = normalizeSlackEmojiName(emoji);
|
|
4786
5187
|
if (!normalizedEmoji) {
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
};
|
|
5188
|
+
throw new ToolInputError(
|
|
5189
|
+
"Emoji must be a valid Slack emoji alias name (for example `thumbsup` or `thumbsup::skin-tone-6`)."
|
|
5190
|
+
);
|
|
4791
5191
|
}
|
|
4792
5192
|
const operationKey = createOperationKey("slackMessageAddReaction", {
|
|
4793
5193
|
channel_id: targetChannelId,
|
|
@@ -5750,6 +6150,31 @@ function createSlackListUpdateItemTool(state) {
|
|
|
5750
6150
|
// src/chat/tools/slack/thread-read.ts
|
|
5751
6151
|
import { Type as Type19 } from "@sinclair/typebox";
|
|
5752
6152
|
|
|
6153
|
+
// src/chat/tools/slack/channel-access.ts
|
|
6154
|
+
async function checkSlackChannelReadAccess(args) {
|
|
6155
|
+
const target = normalizeSlackConversationId(args.targetChannelId);
|
|
6156
|
+
if (!target) {
|
|
6157
|
+
return { allowed: false, error: "Invalid Slack channel ID." };
|
|
6158
|
+
}
|
|
6159
|
+
const currentChannels = args.currentChannelIds.map((channelId) => normalizeSlackConversationId(channelId)).filter((channelId) => Boolean(channelId));
|
|
6160
|
+
if (currentChannels.includes(target)) {
|
|
6161
|
+
return { allowed: true };
|
|
6162
|
+
}
|
|
6163
|
+
const store = args.store ?? getConversationStore();
|
|
6164
|
+
const visibility = await store.getDestinationVisibility({
|
|
6165
|
+
provider: "slack",
|
|
6166
|
+
providerTenantId: args.teamId,
|
|
6167
|
+
providerDestinationId: target
|
|
6168
|
+
});
|
|
6169
|
+
if (visibility === "public") {
|
|
6170
|
+
return { allowed: true };
|
|
6171
|
+
}
|
|
6172
|
+
return {
|
|
6173
|
+
allowed: false,
|
|
6174
|
+
error: "Cannot read this Slack conversation: only the current conversation or public channels Junior has seen in this workspace are readable."
|
|
6175
|
+
};
|
|
6176
|
+
}
|
|
6177
|
+
|
|
5753
6178
|
// src/chat/tools/slack/slack-message-url.ts
|
|
5754
6179
|
var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
|
|
5755
6180
|
var ARCHIVE_PATH_PATTERN = /^\/archives\/([CDG][A-Z0-9]+)\/p(\d{10})(\d{6})$/;
|
|
@@ -5909,26 +6334,9 @@ function truncateMessages(messages, maxChars) {
|
|
|
5909
6334
|
}
|
|
5910
6335
|
return { messages: kept, omitted: messages.length - kept.length };
|
|
5911
6336
|
}
|
|
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) {
|
|
6337
|
+
function createSlackThreadReadTool(context, deps = {}) {
|
|
5930
6338
|
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.
|
|
6339
|
+
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
6340
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5933
6341
|
inputSchema: Type19.Object({
|
|
5934
6342
|
url: Type19.Optional(
|
|
@@ -5988,10 +6396,15 @@ function createSlackThreadReadTool(context) {
|
|
|
5988
6396
|
error: "Provide either a Slack message `url` or both `channel_id` and `ts`."
|
|
5989
6397
|
};
|
|
5990
6398
|
}
|
|
5991
|
-
const access =
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
6399
|
+
const access = await checkSlackChannelReadAccess({
|
|
6400
|
+
currentChannelIds: [
|
|
6401
|
+
context.destinationChannelId,
|
|
6402
|
+
context.sourceChannelId
|
|
6403
|
+
],
|
|
6404
|
+
store: deps.visibilityStore,
|
|
6405
|
+
targetChannelId: channelId,
|
|
6406
|
+
teamId: context.teamId
|
|
6407
|
+
});
|
|
5995
6408
|
if (!access.allowed) {
|
|
5996
6409
|
return {
|
|
5997
6410
|
ok: false,
|
|
@@ -10518,33 +10931,6 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10518
10931
|
}
|
|
10519
10932
|
|
|
10520
10933
|
// src/chat/services/reply-delivery-plan.ts
|
|
10521
|
-
var REACTION_ONLY_ACK_RE = /^(?::[a-z0-9_+-]+:|[\p{Extended_Pictographic}\uFE0F\u200D]+)$/u;
|
|
10522
|
-
var REDUNDANT_REACTION_ACK_TEXT = ["done", "got it", "ok", "okay"];
|
|
10523
|
-
var REACTION_INTENT_RE = /\b(react|reaction|emoji|thumbs?\s*up|acknowledge)\b/i;
|
|
10524
|
-
var REACTION_WITH_REPLY_INTENT_RE = /\b(confirm|explain|reply|respond|say|tell|summari[sz]e|why)\b/i;
|
|
10525
|
-
function normalizeReactionAckText(text) {
|
|
10526
|
-
return text.trim().toLowerCase().replace(/[!.]+$/g, "");
|
|
10527
|
-
}
|
|
10528
|
-
function isRedundantReactionAckText(text) {
|
|
10529
|
-
const trimmed = text.trim();
|
|
10530
|
-
if (!trimmed) {
|
|
10531
|
-
return false;
|
|
10532
|
-
}
|
|
10533
|
-
if (REACTION_ONLY_ACK_RE.test(trimmed)) {
|
|
10534
|
-
return true;
|
|
10535
|
-
}
|
|
10536
|
-
const normalized = normalizeReactionAckText(text);
|
|
10537
|
-
return REDUNDANT_REACTION_ACK_TEXT.includes(
|
|
10538
|
-
normalized
|
|
10539
|
-
);
|
|
10540
|
-
}
|
|
10541
|
-
function isReactionOnlyIntent(text) {
|
|
10542
|
-
const normalized = text.trim();
|
|
10543
|
-
if (!normalized) {
|
|
10544
|
-
return false;
|
|
10545
|
-
}
|
|
10546
|
-
return REACTION_INTENT_RE.test(normalized) && !REACTION_WITH_REPLY_INTENT_RE.test(normalized);
|
|
10547
|
-
}
|
|
10548
10934
|
function buildReplyDeliveryPlan(args) {
|
|
10549
10935
|
const mode = args.explicitChannelPostIntent && args.channelPostPerformed ? "channel_only" : "thread";
|
|
10550
10936
|
let attachFiles = "none";
|
|
@@ -10658,9 +11044,12 @@ function buildTurnResult(input) {
|
|
|
10658
11044
|
const toolResults = newMessages.filter(isToolResultMessage);
|
|
10659
11045
|
const assistantMessages = newMessages.filter(isAssistantMessage);
|
|
10660
11046
|
const terminalAssistantMessages = getTerminalAssistantMessages(newMessages);
|
|
10661
|
-
const
|
|
11047
|
+
const rawPrimaryText = stripThinkingXmlBlocks(
|
|
10662
11048
|
terminalAssistantMessages.map((message) => extractAssistantText(message)).join("\n\n")
|
|
10663
11049
|
).trim();
|
|
11050
|
+
const exactNoReplyMarker = isNoReplyMarker(rawPrimaryText);
|
|
11051
|
+
const mixedNoReplyMarker = !exactNoReplyMarker && containsNoReplyMarker(rawPrimaryText);
|
|
11052
|
+
const primaryText = exactNoReplyMarker ? "" : mixedNoReplyMarker ? stripNoReplyMarker(rawPrimaryText) : rawPrimaryText;
|
|
10664
11053
|
const toolErrorCount = toolResults.filter((result) => result.isError).length;
|
|
10665
11054
|
const explicitChannelPostIntent = isExplicitChannelPostIntent(userInput);
|
|
10666
11055
|
const successfulToolNames = new Set(
|
|
@@ -10671,19 +11060,51 @@ function buildTurnResult(input) {
|
|
|
10671
11060
|
);
|
|
10672
11061
|
const canvasCreated = successfulToolNames.has("slackCanvasCreate");
|
|
10673
11062
|
const reactionPerformed = successfulToolNames.has("slackMessageAddReaction");
|
|
11063
|
+
const markerSideEffectSuccess = exactNoReplyMarker && toolErrorCount === 0 && (reactionPerformed || channelPostPerformed || replyFiles.length > 0);
|
|
11064
|
+
const fileOnlySuccess = !rawPrimaryText && toolErrorCount === 0 && replyFiles.length > 0;
|
|
11065
|
+
const sideEffectOnlySuccess = markerSideEffectSuccess || fileOnlySuccess;
|
|
10674
11066
|
const baseDeliveryPlan = buildReplyDeliveryPlan({
|
|
10675
|
-
explicitChannelPostIntent,
|
|
11067
|
+
explicitChannelPostIntent: exactNoReplyMarker && explicitChannelPostIntent,
|
|
10676
11068
|
channelPostPerformed,
|
|
10677
11069
|
hasFiles: replyFiles.length > 0
|
|
10678
11070
|
});
|
|
10679
|
-
const sideEffectOnlySuccess = !primaryText && toolErrorCount === 0 && (reactionPerformed || channelPostPerformed || replyFiles.length > 0);
|
|
10680
11071
|
const lastAssistant = terminalAssistantMessages.at(-1);
|
|
10681
11072
|
const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
|
|
10682
11073
|
const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
10683
11074
|
const isProviderError = stopReason === "error";
|
|
10684
|
-
if (
|
|
11075
|
+
if (exactNoReplyMarker) {
|
|
11076
|
+
const markerCategory = reactionPerformed ? "reaction" : channelPostPerformed ? "channel_post" : replyFiles.length > 0 ? "file" : "none";
|
|
11077
|
+
const markerContext = {
|
|
11078
|
+
slackThreadId: correlation?.threadId,
|
|
11079
|
+
slackUserId: correlation?.requesterId,
|
|
11080
|
+
slackChannelId: correlation?.channelId,
|
|
11081
|
+
runId: correlation?.runId,
|
|
11082
|
+
assistantUserName,
|
|
11083
|
+
modelId: botConfig.modelId
|
|
11084
|
+
};
|
|
11085
|
+
const markerAttributes = {
|
|
11086
|
+
"app.ai.no_reply_marker": true,
|
|
11087
|
+
"app.ai.no_reply_marker_category": markerCategory,
|
|
11088
|
+
"app.ai.no_reply_marker_accepted": sideEffectOnlySuccess && !isProviderError
|
|
11089
|
+
};
|
|
11090
|
+
if (sideEffectOnlySuccess && !isProviderError) {
|
|
11091
|
+
logInfo(
|
|
11092
|
+
"ai_no_reply_marker_accepted",
|
|
11093
|
+
markerContext,
|
|
11094
|
+
markerAttributes,
|
|
11095
|
+
"No-reply marker suppressed visible thread text"
|
|
11096
|
+
);
|
|
11097
|
+
} else if (!isProviderError) {
|
|
11098
|
+
logWarn(
|
|
11099
|
+
"ai_no_reply_marker_rejected",
|
|
11100
|
+
markerContext,
|
|
11101
|
+
markerAttributes,
|
|
11102
|
+
"No-reply marker requires a successful visible side effect"
|
|
11103
|
+
);
|
|
11104
|
+
}
|
|
11105
|
+
} else if (mixedNoReplyMarker) {
|
|
10685
11106
|
logWarn(
|
|
10686
|
-
"
|
|
11107
|
+
"ai_no_reply_marker_mixed_text",
|
|
10687
11108
|
{
|
|
10688
11109
|
slackThreadId: correlation?.threadId,
|
|
10689
11110
|
slackUserId: correlation?.requesterId,
|
|
@@ -10693,14 +11114,32 @@ function buildTurnResult(input) {
|
|
|
10693
11114
|
modelId: botConfig.modelId
|
|
10694
11115
|
},
|
|
10695
11116
|
{
|
|
10696
|
-
"app.ai.
|
|
10697
|
-
"app.ai.
|
|
10698
|
-
"app.ai.generated_files": input.generatedFileCount
|
|
11117
|
+
"app.ai.no_reply_marker": true,
|
|
11118
|
+
"app.ai.no_reply_marker_mode": "mixed"
|
|
10699
11119
|
},
|
|
10700
|
-
"
|
|
11120
|
+
"No-reply marker appeared with visible assistant text"
|
|
10701
11121
|
);
|
|
10702
11122
|
}
|
|
10703
|
-
|
|
11123
|
+
if (!primaryText && !sideEffectOnlySuccess && !isProviderError && !exactNoReplyMarker) {
|
|
11124
|
+
logWarn(
|
|
11125
|
+
"ai_model_response_empty",
|
|
11126
|
+
{
|
|
11127
|
+
slackThreadId: correlation?.threadId,
|
|
11128
|
+
slackUserId: correlation?.requesterId,
|
|
11129
|
+
slackChannelId: correlation?.channelId,
|
|
11130
|
+
runId: correlation?.runId,
|
|
11131
|
+
assistantUserName,
|
|
11132
|
+
modelId: botConfig.modelId
|
|
11133
|
+
},
|
|
11134
|
+
{
|
|
11135
|
+
"app.ai.tool_results": toolResults.length,
|
|
11136
|
+
"app.ai.tool_error_results": toolErrorCount,
|
|
11137
|
+
"app.ai.generated_files": input.generatedFileCount
|
|
11138
|
+
},
|
|
11139
|
+
"Model returned empty text response"
|
|
11140
|
+
);
|
|
11141
|
+
}
|
|
11142
|
+
const usedPrimaryText = Boolean(rawPrimaryText);
|
|
10704
11143
|
let outcome;
|
|
10705
11144
|
if (isProviderError) {
|
|
10706
11145
|
outcome = "provider_error";
|
|
@@ -10709,8 +11148,7 @@ function buildTurnResult(input) {
|
|
|
10709
11148
|
} else {
|
|
10710
11149
|
outcome = "execution_failure";
|
|
10711
11150
|
}
|
|
10712
|
-
const
|
|
10713
|
-
const rawResponseText = suppressReactionOnlyText ? "" : primaryText;
|
|
11151
|
+
const rawResponseText = primaryText;
|
|
10714
11152
|
const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
|
|
10715
11153
|
const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
|
|
10716
11154
|
const resolvedText = escapedOrRawPayload ? "" : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
|
|
@@ -10860,513 +11298,133 @@ function buildClassifierPrompt(args) {
|
|
|
10860
11298
|
sections.push(
|
|
10861
11299
|
"<thread-background>",
|
|
10862
11300
|
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 } : {}
|
|
11301
|
+
"</thread-background>",
|
|
11302
|
+
""
|
|
11303
|
+
);
|
|
11304
|
+
}
|
|
11305
|
+
}
|
|
11306
|
+
sections.push(renderCurrentInstruction(args.messageText.trim() || "[empty]"));
|
|
11307
|
+
for (const block of args.currentTurnBlocks ?? []) {
|
|
11308
|
+
const trimmed = block.trim();
|
|
11309
|
+
if (!trimmed) {
|
|
11310
|
+
continue;
|
|
11311
|
+
}
|
|
11312
|
+
sections.push("", trimmed);
|
|
11313
|
+
}
|
|
11314
|
+
return sections.join("\n");
|
|
11315
|
+
}
|
|
11316
|
+
async function selectTurnThinkingLevel(args) {
|
|
11317
|
+
const trimmedContext = trimContextForRouter(args.conversationContext);
|
|
11318
|
+
const instructionLength = args.messageText.trim().length;
|
|
11319
|
+
const turnBlockCount = (args.currentTurnBlocks ?? []).filter(
|
|
11320
|
+
(block) => block.trim().length > 0
|
|
11321
|
+
).length;
|
|
11322
|
+
const prompt = buildClassifierPrompt({
|
|
11323
|
+
conversationContext: trimmedContext,
|
|
11324
|
+
currentTurnBlocks: args.currentTurnBlocks,
|
|
11325
|
+
messageText: args.messageText
|
|
11326
|
+
});
|
|
11327
|
+
const logContext = {
|
|
11328
|
+
slackThreadId: args.context?.threadId,
|
|
11329
|
+
slackChannelId: args.context?.channelId,
|
|
11330
|
+
slackUserId: args.context?.requesterId,
|
|
11331
|
+
runId: args.context?.runId,
|
|
11332
|
+
modelId: args.fastModelId
|
|
11333
|
+
};
|
|
11334
|
+
return withSpan(
|
|
11335
|
+
"chat.route_thinking",
|
|
11336
|
+
"chat.route_thinking",
|
|
11337
|
+
logContext,
|
|
11338
|
+
async () => {
|
|
11339
|
+
setSpanAttributes({
|
|
11340
|
+
"app.ai.router.prompt_char_count": prompt.length,
|
|
11341
|
+
"app.ai.router.instruction_char_count": instructionLength,
|
|
11342
|
+
"app.ai.router.context_char_count": trimmedContext?.originalCharCount ?? 0,
|
|
11343
|
+
"app.ai.router.context_trimmed": trimmedContext?.truncated ?? false,
|
|
11344
|
+
"app.ai.router.turn_block_count": turnBlockCount
|
|
11345
|
+
});
|
|
11346
|
+
const selection = await classifyTurn({
|
|
11347
|
+
completeObject: args.completeObject,
|
|
11348
|
+
fastModelId: args.fastModelId,
|
|
11349
|
+
metadata: {
|
|
11350
|
+
modelId: args.fastModelId,
|
|
11351
|
+
threadId: args.context?.threadId ?? "",
|
|
11352
|
+
channelId: args.context?.channelId ?? "",
|
|
11353
|
+
requesterId: args.context?.requesterId ?? "",
|
|
11354
|
+
runId: args.context?.runId ?? ""
|
|
11355
|
+
},
|
|
11356
|
+
prompt
|
|
11357
|
+
});
|
|
11358
|
+
const normalizedSelection = applyThinkingFloor(selection, {
|
|
11359
|
+
minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
|
|
11360
|
+
});
|
|
11361
|
+
setSpanAttributes({
|
|
11362
|
+
"app.ai.thinking_level": normalizedSelection.thinkingLevel,
|
|
11363
|
+
"app.ai.thinking_level_reason": normalizedSelection.reason,
|
|
11364
|
+
...normalizedSelection.confidence !== void 0 ? {
|
|
11365
|
+
"app.ai.thinking_level_confidence": normalizedSelection.confidence
|
|
11366
|
+
} : {}
|
|
11286
11367
|
});
|
|
11368
|
+
return normalizedSelection;
|
|
11287
11369
|
}
|
|
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;
|
|
11370
|
+
);
|
|
11371
|
+
}
|
|
11372
|
+
function applyThinkingFloor(selection, args) {
|
|
11373
|
+
const minimum = args.minimum;
|
|
11374
|
+
if (!minimum || selection.thinkingLevel === "none" || THINKING_LEVEL_RANK[selection.thinkingLevel] >= THINKING_LEVEL_RANK[minimum]) {
|
|
11375
|
+
return selection;
|
|
11319
11376
|
}
|
|
11377
|
+
return {
|
|
11378
|
+
...selection,
|
|
11379
|
+
thinkingLevel: minimum,
|
|
11380
|
+
reason: `thinking_floor:${minimum}:${selection.reason}`
|
|
11381
|
+
};
|
|
11320
11382
|
}
|
|
11321
|
-
async function
|
|
11383
|
+
async function classifyTurn(args) {
|
|
11322
11384
|
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 } : {}
|
|
11385
|
+
const result = await args.completeObject({
|
|
11386
|
+
modelId: args.fastModelId,
|
|
11387
|
+
schema: turnExecutionProfileSchema,
|
|
11388
|
+
maxTokens: 120,
|
|
11389
|
+
metadata: args.metadata,
|
|
11390
|
+
prompt: args.prompt,
|
|
11391
|
+
thinkingLevel: "low",
|
|
11392
|
+
system: buildClassifierSystemPrompt(),
|
|
11393
|
+
temperature: 0
|
|
11358
11394
|
});
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
}
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11395
|
+
const parsed = turnExecutionProfileSchema.parse(result.object);
|
|
11396
|
+
const reason = parsed.reason.trim();
|
|
11397
|
+
if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
|
|
11398
|
+
return {
|
|
11399
|
+
confidence: parsed.confidence,
|
|
11400
|
+
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
11401
|
+
reason: `low_confidence_medium_default:${reason}`
|
|
11402
|
+
};
|
|
11403
|
+
}
|
|
11404
|
+
return {
|
|
11405
|
+
confidence: parsed.confidence,
|
|
11406
|
+
thinkingLevel: parsed.thinking_level,
|
|
11407
|
+
reason
|
|
11408
|
+
};
|
|
11409
|
+
} catch {
|
|
11410
|
+
return {
|
|
11411
|
+
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
11412
|
+
reason: "classifier_error_default"
|
|
11413
|
+
};
|
|
11414
|
+
}
|
|
11415
|
+
}
|
|
11416
|
+
function toAgentThinkingLevel(level) {
|
|
11417
|
+
switch (level) {
|
|
11418
|
+
case "none":
|
|
11419
|
+
return "off";
|
|
11420
|
+
case "low":
|
|
11421
|
+
return "low";
|
|
11422
|
+
case "medium":
|
|
11423
|
+
return "medium";
|
|
11424
|
+
case "high":
|
|
11425
|
+
return "high";
|
|
11426
|
+
case "xhigh":
|
|
11427
|
+
return "xhigh";
|
|
11370
11428
|
}
|
|
11371
11429
|
}
|
|
11372
11430
|
|
|
@@ -13051,7 +13109,10 @@ function legacyTextPartMatchesCurrentText(storedText, currentText) {
|
|
|
13051
13109
|
async function generateAssistantReply(messageText2, context) {
|
|
13052
13110
|
const conversationPrivacy = resolveConversationPrivacy({
|
|
13053
13111
|
channelId: context.correlation?.channelId,
|
|
13054
|
-
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId
|
|
13112
|
+
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
13113
|
+
// Source-confirmed visibility from the live event's channel_type; without
|
|
13114
|
+
// it the turn fails closed to private telemetry capture.
|
|
13115
|
+
visibility: context.slackConversation?.visibility
|
|
13055
13116
|
});
|
|
13056
13117
|
return runWithConversationPrivacy(
|
|
13057
13118
|
conversationPrivacy ?? "private",
|
|
@@ -13214,14 +13275,26 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13214
13275
|
if (!turnSessionState.canUseTurnSession || !sessionConversationId || !sessionId) {
|
|
13215
13276
|
return;
|
|
13216
13277
|
}
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13278
|
+
try {
|
|
13279
|
+
await recordToolExecutionStarted({
|
|
13280
|
+
conversationId: sessionConversationId,
|
|
13281
|
+
sessionId,
|
|
13282
|
+
toolCallId: event.toolCallId,
|
|
13283
|
+
toolName: event.toolName,
|
|
13284
|
+
args: event.args,
|
|
13285
|
+
ttlMs: THREAD_STATE_TTL_MS4
|
|
13286
|
+
});
|
|
13287
|
+
} catch (error) {
|
|
13288
|
+
logException(
|
|
13289
|
+
error,
|
|
13290
|
+
"agent_turn_session_log_append_failed",
|
|
13291
|
+
spanContext,
|
|
13292
|
+
{
|
|
13293
|
+
"gen_ai.tool.name": event.toolName
|
|
13294
|
+
},
|
|
13295
|
+
"Failed to record host-only tool execution start"
|
|
13296
|
+
);
|
|
13297
|
+
}
|
|
13225
13298
|
};
|
|
13226
13299
|
const persistedConfigurationValues = context.channelConfiguration ? await context.channelConfiguration.resolveValues() : {};
|
|
13227
13300
|
configurationValues = {
|
|
@@ -13581,10 +13654,10 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13581
13654
|
);
|
|
13582
13655
|
const hasPromptCheckpoint = resumedFromSessionRecord && existingSessionRecord?.turnStartMessageIndex !== void 0;
|
|
13583
13656
|
const shouldPromptAgent = !resumedFromSessionRecord || !hasPromptCheckpoint;
|
|
13584
|
-
const promptHistoryMessages = shouldPromptAgent
|
|
13657
|
+
const promptHistoryMessages = shouldPromptAgent ? withoutTrailingUncheckpointedUserPrompt(
|
|
13585
13658
|
priorPiMessages,
|
|
13586
13659
|
userContentParts
|
|
13587
|
-
) :
|
|
13660
|
+
) : existingSessionRecord.piMessages;
|
|
13588
13661
|
const needsBootstrapContextForPrompt = shouldPromptAgent && !hasRuntimeTurnContext(promptHistoryMessages);
|
|
13589
13662
|
const systemPromptContributions = await getPluginSystemPromptContributions(toolSource);
|
|
13590
13663
|
const pluginSystemPrompt = buildPluginSystemPromptContributions(
|
|
@@ -13739,12 +13812,12 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13739
13812
|
});
|
|
13740
13813
|
if (steeredMessageCount > 0) {
|
|
13741
13814
|
logInfo(
|
|
13742
|
-
"
|
|
13815
|
+
"agent_turn_steering_messages_accepted",
|
|
13743
13816
|
spanContext,
|
|
13744
13817
|
{
|
|
13745
13818
|
"app.ai.steering_message_count": steeredMessageCount
|
|
13746
13819
|
},
|
|
13747
|
-
"Agent turn steering messages
|
|
13820
|
+
"Agent turn steering messages accepted"
|
|
13748
13821
|
);
|
|
13749
13822
|
}
|
|
13750
13823
|
} catch (error) {
|
|
@@ -13835,10 +13908,10 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
13835
13908
|
beforeMessageCount = agent.state.messages.length;
|
|
13836
13909
|
try {
|
|
13837
13910
|
if (resumedFromSessionRecord) {
|
|
13838
|
-
agent.state.messages = shouldPromptAgent ? promptHistoryMessages
|
|
13911
|
+
agent.state.messages = shouldPromptAgent ? promptHistoryMessages : existingSessionRecord.piMessages;
|
|
13839
13912
|
turnStartMessageIndex = existingSessionRecord.turnStartMessageIndex;
|
|
13840
|
-
} else if (
|
|
13841
|
-
agent.state.messages = [...
|
|
13913
|
+
} else if (promptHistoryMessages.length > 0) {
|
|
13914
|
+
agent.state.messages = [...promptHistoryMessages];
|
|
13842
13915
|
}
|
|
13843
13916
|
beforeMessageCount = agent.state.messages.length;
|
|
13844
13917
|
if (shouldPromptAgent) {
|
|
@@ -14006,22 +14079,6 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
14006
14079
|
}
|
|
14007
14080
|
if (turnSessionState.canUseTurnSession && sessionConversationId && sessionId) {
|
|
14008
14081
|
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
14082
|
}
|
|
14026
14083
|
return buildTurnResult({
|
|
14027
14084
|
newMessages,
|
|
@@ -14183,7 +14240,7 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
|
|
|
14183
14240
|
);
|
|
14184
14241
|
const message = error instanceof Error ? error.message : String(error);
|
|
14185
14242
|
return {
|
|
14186
|
-
text:
|
|
14243
|
+
text: "",
|
|
14187
14244
|
...getSandboxMetadata(),
|
|
14188
14245
|
diagnostics: {
|
|
14189
14246
|
outcome: "provider_error",
|
|
@@ -14574,6 +14631,98 @@ function getConversationMessageSlackTs(message) {
|
|
|
14574
14631
|
return message.meta?.slackTs ?? toOptionalString(message.id);
|
|
14575
14632
|
}
|
|
14576
14633
|
|
|
14634
|
+
// src/chat/services/turn-failure-response.ts
|
|
14635
|
+
function requireTurnFailureEventId(eventId, eventName) {
|
|
14636
|
+
if (!eventId) {
|
|
14637
|
+
throw new Error(`Sentry did not return an event ID for ${eventName}`);
|
|
14638
|
+
}
|
|
14639
|
+
return eventId;
|
|
14640
|
+
}
|
|
14641
|
+
function getExecutionFailureReason(reply) {
|
|
14642
|
+
const errorMessage = reply.diagnostics.errorMessage?.trim();
|
|
14643
|
+
if (errorMessage) {
|
|
14644
|
+
return errorMessage;
|
|
14645
|
+
}
|
|
14646
|
+
if (reply.diagnostics.toolErrorCount > 0) {
|
|
14647
|
+
return `${reply.diagnostics.toolErrorCount} tool result error(s)`;
|
|
14648
|
+
}
|
|
14649
|
+
if (reply.diagnostics.assistantMessageCount > 0) {
|
|
14650
|
+
return "assistant returned no text";
|
|
14651
|
+
}
|
|
14652
|
+
return "empty assistant turn";
|
|
14653
|
+
}
|
|
14654
|
+
function getFailureCapture(reply) {
|
|
14655
|
+
if (reply.diagnostics.outcome === "provider_error") {
|
|
14656
|
+
return {
|
|
14657
|
+
eventName: "agent_turn_provider_error",
|
|
14658
|
+
error: reply.diagnostics.providerError ?? new Error(
|
|
14659
|
+
reply.diagnostics.errorMessage ?? "Provider error without explicit message"
|
|
14660
|
+
),
|
|
14661
|
+
attributes: {},
|
|
14662
|
+
body: "Agent turn failed with provider error"
|
|
14663
|
+
};
|
|
14664
|
+
}
|
|
14665
|
+
const failureReason = getExecutionFailureReason(reply);
|
|
14666
|
+
return {
|
|
14667
|
+
eventName: "agent_turn_execution_failure",
|
|
14668
|
+
error: new Error(`Agent turn execution failure: ${failureReason}`),
|
|
14669
|
+
attributes: {
|
|
14670
|
+
"app.ai.execution_failure_reason": failureReason
|
|
14671
|
+
},
|
|
14672
|
+
body: "Agent turn completed with execution failure"
|
|
14673
|
+
};
|
|
14674
|
+
}
|
|
14675
|
+
function getAgentTurnDiagnosticsAttributes(reply) {
|
|
14676
|
+
return {
|
|
14677
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
14678
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
14679
|
+
"app.ai.outcome": reply.diagnostics.outcome,
|
|
14680
|
+
"app.ai.assistant_messages": reply.diagnostics.assistantMessageCount,
|
|
14681
|
+
"app.ai.tool_results": reply.diagnostics.toolResultCount,
|
|
14682
|
+
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
|
|
14683
|
+
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
14684
|
+
"app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
|
|
14685
|
+
...reply.diagnostics.thinkingLevel ? {
|
|
14686
|
+
"app.ai.reasoning_effort": reply.diagnostics.thinkingLevel
|
|
14687
|
+
} : {},
|
|
14688
|
+
...reply.diagnostics.stopReason ? {
|
|
14689
|
+
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
14690
|
+
} : {},
|
|
14691
|
+
...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
|
|
14692
|
+
};
|
|
14693
|
+
}
|
|
14694
|
+
function finalizeFailedTurnReply(args) {
|
|
14695
|
+
if (args.reply.diagnostics.outcome === "success") {
|
|
14696
|
+
return args.reply;
|
|
14697
|
+
}
|
|
14698
|
+
const capture = getFailureCapture(args.reply);
|
|
14699
|
+
const eventId = requireTurnFailureEventId(
|
|
14700
|
+
args.logException(
|
|
14701
|
+
capture.error,
|
|
14702
|
+
capture.eventName,
|
|
14703
|
+
args.context,
|
|
14704
|
+
{
|
|
14705
|
+
...getAgentTurnDiagnosticsAttributes(args.reply),
|
|
14706
|
+
...args.attributes,
|
|
14707
|
+
...capture.attributes
|
|
14708
|
+
},
|
|
14709
|
+
capture.body
|
|
14710
|
+
),
|
|
14711
|
+
capture.eventName
|
|
14712
|
+
);
|
|
14713
|
+
const providerPartialText = args.reply.diagnostics.outcome === "provider_error" && args.reply.diagnostics.assistantMessageCount > 0 ? args.reply.text.trim() : "";
|
|
14714
|
+
return {
|
|
14715
|
+
...args.reply,
|
|
14716
|
+
text: providerPartialText ? `${providerPartialText}${getInterruptionMarker()}` : buildTurnFailureResponse(eventId),
|
|
14717
|
+
deliveryMode: "thread",
|
|
14718
|
+
deliveryPlan: {
|
|
14719
|
+
mode: "thread",
|
|
14720
|
+
postThreadText: true,
|
|
14721
|
+
attachFiles: args.reply.files && args.reply.files.length > 0 ? "inline" : "none"
|
|
14722
|
+
}
|
|
14723
|
+
};
|
|
14724
|
+
}
|
|
14725
|
+
|
|
14577
14726
|
// src/chat/plugins/task-runner.ts
|
|
14578
14727
|
import { pluginRunContextSchema } from "@sentry/junior-plugin-api";
|
|
14579
14728
|
var PLUGIN_TASK_LOCK_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -14763,6 +14912,7 @@ async function processPluginTask(message, options = {}) {
|
|
|
14763
14912
|
}
|
|
14764
14913
|
|
|
14765
14914
|
// src/chat/runtime/delivered-turn-state.ts
|
|
14915
|
+
var NO_VISIBLE_REPLY_CONVERSATION_TEXT = "[no visible reply]";
|
|
14766
14916
|
function buildDeliveredTurnStatePatch(args) {
|
|
14767
14917
|
const conversation = structuredClone(args.conversation);
|
|
14768
14918
|
const artifactStatePatch = {
|
|
@@ -14771,6 +14921,7 @@ function buildDeliveredTurnStatePatch(args) {
|
|
|
14771
14921
|
};
|
|
14772
14922
|
const artifacts = Object.keys(artifactStatePatch).length > 0 ? mergeArtifactsState(args.artifacts, artifactStatePatch) : void 0;
|
|
14773
14923
|
clearPendingAuth(conversation, args.sessionId);
|
|
14924
|
+
const assistantText = normalizeConversationText(args.reply.text) || (args.reply.deliveryPlan?.postThreadText === false ? NO_VISIBLE_REPLY_CONVERSATION_TEXT : "[empty response]");
|
|
14774
14925
|
markConversationMessage(conversation, args.userMessageId, {
|
|
14775
14926
|
replied: true,
|
|
14776
14927
|
skippedReason: void 0
|
|
@@ -14778,7 +14929,7 @@ function buildDeliveredTurnStatePatch(args) {
|
|
|
14778
14929
|
upsertConversationMessage(conversation, {
|
|
14779
14930
|
id: generateConversationId("assistant"),
|
|
14780
14931
|
role: "assistant",
|
|
14781
|
-
text:
|
|
14932
|
+
text: assistantText,
|
|
14782
14933
|
createdAtMs: Date.now(),
|
|
14783
14934
|
author: {
|
|
14784
14935
|
userName: botConfig.userName,
|
|
@@ -14810,7 +14961,6 @@ export {
|
|
|
14810
14961
|
deliverResourceEventSubscription,
|
|
14811
14962
|
listThreadReplies,
|
|
14812
14963
|
postSlackMessage,
|
|
14813
|
-
deleteSlackMessage,
|
|
14814
14964
|
uploadFilesToThread,
|
|
14815
14965
|
addReactionToMessage,
|
|
14816
14966
|
removeReactionFromMessage,
|
|
@@ -14849,13 +14999,18 @@ export {
|
|
|
14849
14999
|
isCooperativeTurnYieldError,
|
|
14850
15000
|
TurnInputCommitLostError,
|
|
14851
15001
|
isTurnInputCommitLostError,
|
|
15002
|
+
TurnInputDeferredError,
|
|
15003
|
+
isTurnInputDeferredError,
|
|
14852
15004
|
startActiveTurn,
|
|
14853
15005
|
markTurnClosed,
|
|
14854
15006
|
markTurnFailed,
|
|
14855
|
-
|
|
15007
|
+
persistCompletedSessionRecord,
|
|
15008
|
+
completeDeliveredTurn,
|
|
15009
|
+
persistYieldSessionRecord,
|
|
14856
15010
|
finalizeMcpAuthorization,
|
|
14857
15011
|
resolvePluginOAuthAccount,
|
|
14858
15012
|
executeCredentialedEgressRequest,
|
|
15013
|
+
buildSteeringPiMessage,
|
|
14859
15014
|
generateAssistantReply,
|
|
14860
15015
|
estimateTextTokens,
|
|
14861
15016
|
getAgentContextCompactionTriggerTokens,
|
|
@@ -14869,6 +15024,9 @@ export {
|
|
|
14869
15024
|
createConversationMemoryService,
|
|
14870
15025
|
isHumanConversationMessage,
|
|
14871
15026
|
getConversationMessageSlackTs,
|
|
15027
|
+
requireTurnFailureEventId,
|
|
15028
|
+
getAgentTurnDiagnosticsAttributes,
|
|
15029
|
+
finalizeFailedTurnReply,
|
|
14872
15030
|
scheduleSessionCompletedPluginTasks,
|
|
14873
15031
|
processPluginTask,
|
|
14874
15032
|
buildDeliveredTurnStatePatch
|