@sentry/junior 0.203.0 → 0.205.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-D4nu8MQV.js +17521 -0
- package/dist/agent-hooks-DXHpTJ4m.js +4042 -0
- package/dist/agent-runner-C04colea.js +678 -0
- package/dist/api/schema.d.ts +2736 -2639
- package/dist/api/schema.js +4 -169
- package/dist/api.d.ts +23 -17
- package/dist/api.js +3881 -5326
- package/dist/app-Cb776lL8.d.ts +247 -0
- package/dist/app.d.ts +4 -520
- package/dist/app.js +9524 -11875
- package/dist/auth-pause-state-C2P3guS1.js +168 -0
- package/dist/catalog-runtime-CXw3asJD.js +7 -0
- package/dist/checkpoint-CrViJ8Ps.js +2450 -0
- package/dist/cli/chat.d.ts +20 -0
- package/dist/cli/chat.js +229 -336
- package/dist/cli/check.d.ts +8 -0
- package/dist/cli/check.js +513 -806
- package/dist/cli/env.d.ts +13 -0
- package/dist/cli/env.js +63 -12
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.js +90 -159
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.js +43 -64
- package/dist/cli/plugins.d.ts +13 -0
- package/dist/cli/plugins.js +148 -227
- package/dist/cli/run.d.ts +16 -0
- package/dist/cli/run.js +52 -8
- package/dist/cli/snapshot-create.d.ts +4 -0
- package/dist/cli/snapshot-create.js +71 -125
- package/dist/cli/upgrade.d.ts +15 -0
- package/dist/cli/upgrade.js +232 -335
- package/dist/config-BOtJQ7Pc.js +941 -0
- package/dist/context-B45_WqVX.js +298 -0
- package/dist/conversation-privacy-BZRA6Pes.js +182 -0
- package/dist/conversation-work-C6y3T5Hx.js +51 -0
- package/dist/credential-sync-Bep34IB_.js +2661 -0
- package/dist/db-BeyXKJOI.d.ts +5 -0
- package/dist/destinations-BeBiJeEM.js +29 -0
- package/dist/execution-stats-B6lgWxCK.js +373 -0
- package/dist/executor-BlUhFL7_.js +721 -0
- package/dist/experimental-CIXf7Uye.js +46 -0
- package/dist/history-ClE4toxe.d.ts +32 -0
- package/dist/history-CqTuSAdI.js +441 -0
- package/dist/instrumentation.d.ts +3 -3
- package/dist/instrumentation.js +130 -176
- package/dist/job-queue-DLMqIK74.js +63 -0
- package/dist/logging-BMJzdDlP.js +1210 -0
- package/dist/model-profile-CZmVoGdX.js +49 -0
- package/dist/nitro.d.ts +31 -36
- package/dist/nitro.js +275 -456
- package/dist/oauth-callback-server-WwG5_Zgm.js +130 -0
- package/dist/oauth-relay-CdCnu80W.js +62 -0
- package/dist/package-discovery-7a9YqLhE.js +455 -0
- package/dist/pending-auth-CUbXq06G.js +185 -0
- package/dist/plugin-module-BKGOXca2.js +81 -0
- package/dist/plugins-BI9A1vKA.d.ts +95 -0
- package/dist/plugins-egKWjgue.js +99 -0
- package/dist/private-trace-result-BLvBjy_e.js +16 -0
- package/dist/profile-T0tXE4zu.js +861 -0
- package/dist/registry-BA0__8ai.js +1419 -0
- package/dist/reporting-schema-DosTN__Q.js +105 -0
- package/dist/resolve-bvD1ORdv.js +546 -0
- package/dist/rolldown-runtime-B4FMCO8f.js +28 -0
- package/dist/runner-BXYxfIUL.js +392 -0
- package/dist/sandbox-egress-signals-ngEPjSEW.js +46 -0
- package/dist/schema-Rn8-XEaN.js +1140 -0
- package/dist/sentry-CD3SdJl-.js +26 -0
- package/dist/skills-DJOlYdBs.js +316 -0
- package/dist/spawn-h0R1bTEo.js +29 -0
- package/dist/stats-3YZn-Swi.js +2648 -0
- package/dist/store-C5qxF2ac.d.ts +208 -0
- package/dist/task-queue-CoXwd2eq.js +136 -0
- package/dist/task-runner-B2_mJZj0.js +639 -0
- package/dist/thread-state-CqZ1m-es.js +431 -0
- package/dist/turn-execution-fI6PZ0iI.js +47 -0
- package/dist/turn-lifecycle-CcnwrOcp.js +54 -0
- package/dist/usage-B5SfMb3H.js +296 -0
- package/dist/usage-schema-CKaDkH_j.js +20 -0
- package/dist/validation-DfWcvhuS.js +40 -0
- package/dist/vercel-queue-DIkVcfK4.js +167 -0
- package/dist/vercel.d.ts +6 -6
- package/dist/vercel.js +14 -6
- package/dist/version-CnXZ614E.d.ts +5 -0
- package/dist/version-oD-oPrbO.js +8 -0
- package/dist/version.d.ts +2 -4
- package/dist/version.js +2 -6
- package/dist/web-authorization-Crbzlmzo.js +76 -0
- package/dist/web-input-BOuq3oKK.js +494 -0
- package/dist/work-B91LQKHr.js +509 -0
- package/dist/worker-BcRRTe6k.js +625 -0
- package/package.json +9 -9
- package/dist/agent-hooks-GB2VPTNA.js +0 -51
- package/dist/catalog-runtime-UBLIWD3O.js +0 -14
- package/dist/chunk-2LYHWMR4.js +0 -46
- package/dist/chunk-352OWAZH.js +0 -320
- package/dist/chunk-3Y47VMHV.js +0 -6
- package/dist/chunk-3YJSKOWF.js +0 -164
- package/dist/chunk-4KSSKTQ5.js +0 -229
- package/dist/chunk-4UO6FK4G.js +0 -64
- package/dist/chunk-5AHX53NQ.js +0 -717
- package/dist/chunk-5J3E5FWA.js +0 -700
- package/dist/chunk-5PDAG6O3.js +0 -206
- package/dist/chunk-6Y5B5PEX.js +0 -80
- package/dist/chunk-7GWQ5S5R.js +0 -2086
- package/dist/chunk-7H2VZTWN.js +0 -103
- package/dist/chunk-7NL7T6VU.js +0 -406
- package/dist/chunk-A5L27LE4.js +0 -387
- package/dist/chunk-ADIAWL34.js +0 -81
- package/dist/chunk-ASTEMBKJ.js +0 -519
- package/dist/chunk-CEMMLV5L.js +0 -98
- package/dist/chunk-CM5HNV2W.js +0 -131
- package/dist/chunk-CRTRBBRI.js +0 -105
- package/dist/chunk-DA2KGZZI.js +0 -13
- package/dist/chunk-DIB4GX3G.js +0 -8
- package/dist/chunk-DKIJ2RP5.js +0 -68
- package/dist/chunk-DWPN6SCH.js +0 -172
- package/dist/chunk-EDKUFGPL.js +0 -64
- package/dist/chunk-ENY6SKIU.js +0 -1228
- package/dist/chunk-F4MGCZOX.js +0 -2943
- package/dist/chunk-FA6POGLH.js +0 -70
- package/dist/chunk-FSUR7GTG.js +0 -20475
- package/dist/chunk-GKEOHJL5.js +0 -1856
- package/dist/chunk-GMCB6W5Y.js +0 -333
- package/dist/chunk-IE2VPG7O.js +0 -1135
- package/dist/chunk-IZ5B5IHQ.js +0 -3656
- package/dist/chunk-JAL3CH4G.js +0 -27
- package/dist/chunk-KIIEUXD3.js +0 -1764
- package/dist/chunk-KXU4NIL7.js +0 -692
- package/dist/chunk-MTM5IFCY.js +0 -1190
- package/dist/chunk-N7TMSOZH.js +0 -235
- package/dist/chunk-NCEBK6PH.js +0 -36
- package/dist/chunk-NHJU2ZAL.js +0 -56
- package/dist/chunk-P6KTRNVC.js +0 -321
- package/dist/chunk-PDO5BLNM.js +0 -22
- package/dist/chunk-PDRNLLFT.js +0 -3295
- package/dist/chunk-PEQXYSSE.js +0 -45
- package/dist/chunk-PKEX2AQX.js +0 -198
- package/dist/chunk-PSJCGIGO.js +0 -1163
- package/dist/chunk-PZUSUL55.js +0 -506
- package/dist/chunk-R45M4TUS.js +0 -37
- package/dist/chunk-RC745GGZ.js +0 -122
- package/dist/chunk-SP3QKZXJ.js +0 -820
- package/dist/chunk-TXKUKMVO.js +0 -417
- package/dist/chunk-UE4GPWSD.js +0 -931
- package/dist/chunk-UIE3R5XU.js +0 -17
- package/dist/chunk-UXPG6ZIN.js +0 -84
- package/dist/chunk-VFUK3X5B.js +0 -50
- package/dist/chunk-VNMJWREK.js +0 -787
- package/dist/chunk-VZ7CSOX2.js +0 -706
- package/dist/chunk-WCNQJKFT.js +0 -10
- package/dist/chunk-WDZR3QQM.js +0 -135
- package/dist/chunk-WLYVJCHV.js +0 -1433
- package/dist/chunk-WS7NAFTM.js +0 -1520
- package/dist/chunk-X6GDDWQH.js +0 -60
- package/dist/chunk-XPDEYNQI.js +0 -102
- package/dist/chunk-XRW44VGM.js +0 -58
- package/dist/chunk-Y2CM7HXH.js +0 -111
- package/dist/chunk-Y7X25LFY.js +0 -15
- package/dist/conversation-work-VIOQ5ZF3.js +0 -54
- package/dist/db-NGDJV7YH.js +0 -26
- package/dist/discovery-DWVDM7UK.js +0 -44
- package/dist/experimental-P5FZM4CU.js +0 -12
- package/dist/oauth-callback-server-YFY7YEGK.js +0 -243
- package/dist/oauth-relay-5XPVRPYB.js +0 -8
- package/dist/plugins-YTYJMMJF.js +0 -14
- package/dist/runner-ORTNSB2Q.js +0 -522
- package/dist/sandbox-egress-signals-FIL7EGPR.js +0 -73
- package/dist/sentry-MB3NL3IR.js +0 -42
- package/dist/skills-H376WQOH.js +0 -27
- package/dist/spawn-T43WHLOS.js +0 -42
- package/dist/task-runner-LBZWF32V.js +0 -36
- package/dist/validation-REJLRKI6.js +0 -17
- package/dist/work-3KJ3RE76.js +0 -51
- package/dist/worker-QFEBU72E.js +0 -24
package/dist/chunk-F4MGCZOX.js
DELETED
|
@@ -1,2943 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
JUNIOR_NATIVE_EVENT_NAMESPACE,
|
|
3
|
-
ToolInputError,
|
|
4
|
-
addAgentTurnUsage,
|
|
5
|
-
agentsInstructionsUpdatedEvent,
|
|
6
|
-
authenticationLinkedEvent,
|
|
7
|
-
authenticationUnlinkedEvent,
|
|
8
|
-
fallbackShortTitle,
|
|
9
|
-
generateShortTitle
|
|
10
|
-
} from "./chunk-GMCB6W5Y.js";
|
|
11
|
-
import {
|
|
12
|
-
bindProviderConversation,
|
|
13
|
-
createSqlConversationEventStore,
|
|
14
|
-
getConversationEventStore,
|
|
15
|
-
getConversationStore,
|
|
16
|
-
getSqlExecutor,
|
|
17
|
-
sanitizePostgresJson,
|
|
18
|
-
withConversationEventLock
|
|
19
|
-
} from "./chunk-GKEOHJL5.js";
|
|
20
|
-
import {
|
|
21
|
-
SANDBOX_REPOS_ROOT,
|
|
22
|
-
SANDBOX_WORKSPACE_ROOT,
|
|
23
|
-
sleep
|
|
24
|
-
} from "./chunk-5AHX53NQ.js";
|
|
25
|
-
import {
|
|
26
|
-
MUTATION_LOCK_TTL_MS,
|
|
27
|
-
fenceLock,
|
|
28
|
-
getStateAdapter,
|
|
29
|
-
withLock
|
|
30
|
-
} from "./chunk-P6KTRNVC.js";
|
|
31
|
-
import {
|
|
32
|
-
agentHistoryItemSchema,
|
|
33
|
-
contextProvenance,
|
|
34
|
-
instructionActors,
|
|
35
|
-
instructionProvenanceFor
|
|
36
|
-
} from "./chunk-7NL7T6VU.js";
|
|
37
|
-
import {
|
|
38
|
-
botConfig,
|
|
39
|
-
completeText,
|
|
40
|
-
escapeXml,
|
|
41
|
-
resolveGatewayModel,
|
|
42
|
-
unwrapCurrentInstruction
|
|
43
|
-
} from "./chunk-ENY6SKIU.js";
|
|
44
|
-
import {
|
|
45
|
-
parseSlackMessageTs,
|
|
46
|
-
toStoredSlackActor
|
|
47
|
-
} from "./chunk-N7TMSOZH.js";
|
|
48
|
-
import {
|
|
49
|
-
buildTurnFailureResponse,
|
|
50
|
-
getActiveTraceId,
|
|
51
|
-
logException,
|
|
52
|
-
logWarn,
|
|
53
|
-
setSpanAttributes
|
|
54
|
-
} from "./chunk-WS7NAFTM.js";
|
|
55
|
-
|
|
56
|
-
// src/chat/turn-context-tag.ts
|
|
57
|
-
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
58
|
-
|
|
59
|
-
// src/chat/pi/transcript.ts
|
|
60
|
-
var RUNTIME_TURN_CONTEXT_START = `<${TURN_CONTEXT_TAG}>`;
|
|
61
|
-
var AGENTS_INSTRUCTIONS_START = "# AGENTS.md instructions";
|
|
62
|
-
function userMessageContent(message) {
|
|
63
|
-
const record = message;
|
|
64
|
-
return record.role === "user" && Array.isArray(record.content) ? record.content : void 0;
|
|
65
|
-
}
|
|
66
|
-
function isRuntimeTurnContextPart(part) {
|
|
67
|
-
return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && (part.text.startsWith(RUNTIME_TURN_CONTEXT_START) || part.text.startsWith(AGENTS_INSTRUCTIONS_START));
|
|
68
|
-
}
|
|
69
|
-
function hasCurrentInstruction(message) {
|
|
70
|
-
return userMessageContent(message)?.some(
|
|
71
|
-
(part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.includes("<current-instruction")
|
|
72
|
-
) ?? false;
|
|
73
|
-
}
|
|
74
|
-
function messageTimestamp(message) {
|
|
75
|
-
return message.timestamp;
|
|
76
|
-
}
|
|
77
|
-
function isStandaloneRuntimeContextMessage(messages, index) {
|
|
78
|
-
const message = messages[index];
|
|
79
|
-
const next = messages[index + 1];
|
|
80
|
-
return Boolean(
|
|
81
|
-
message && next && userMessageContent(message) && userMessageContent(next) && messageTimestamp(message) === messageTimestamp(next) && !hasCurrentInstruction(message) && hasCurrentInstruction(next)
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
var EMBEDDED_THREAD_CONTEXT_TAGS = [
|
|
85
|
-
"thread-context",
|
|
86
|
-
"recent-thread-messages",
|
|
87
|
-
"thread-compactions",
|
|
88
|
-
"thread-transcript",
|
|
89
|
-
"thread-background"
|
|
90
|
-
];
|
|
91
|
-
var EMBEDDED_THREAD_CONTEXT_PATTERN = new RegExp(
|
|
92
|
-
`<(${EMBEDDED_THREAD_CONTEXT_TAGS.join("|")})(?:\\s[^>]*)?>[\\s\\S]*</\\1>`,
|
|
93
|
-
"g"
|
|
94
|
-
);
|
|
95
|
-
function isAssistantMessage(value) {
|
|
96
|
-
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
97
|
-
}
|
|
98
|
-
function isToolResultMessage(value) {
|
|
99
|
-
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
100
|
-
}
|
|
101
|
-
function isToolResultError(result) {
|
|
102
|
-
if (!result || typeof result !== "object") return false;
|
|
103
|
-
return Boolean(result.isError);
|
|
104
|
-
}
|
|
105
|
-
function normalizeToolNameFromResult(result) {
|
|
106
|
-
if (!result || typeof result !== "object") return void 0;
|
|
107
|
-
const record = result;
|
|
108
|
-
if (typeof record.toolName === "string" && record.toolName.length > 0) {
|
|
109
|
-
return record.toolName;
|
|
110
|
-
}
|
|
111
|
-
if (typeof record.name === "string" && record.name.length > 0) {
|
|
112
|
-
return record.name;
|
|
113
|
-
}
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
116
|
-
function getPiMessageRole(value) {
|
|
117
|
-
if (!value || typeof value !== "object") {
|
|
118
|
-
return void 0;
|
|
119
|
-
}
|
|
120
|
-
const role = value.role;
|
|
121
|
-
return typeof role === "string" ? role : void 0;
|
|
122
|
-
}
|
|
123
|
-
function isContinuablePiBoundary(messages) {
|
|
124
|
-
const lastRole = getPiMessageRole(messages.at(-1));
|
|
125
|
-
return lastRole === "user" || lastRole === "toolResult";
|
|
126
|
-
}
|
|
127
|
-
function extractAssistantText(message) {
|
|
128
|
-
const content = message.content ?? [];
|
|
129
|
-
return content.filter(
|
|
130
|
-
(part) => part.type === "text" && typeof part.text === "string"
|
|
131
|
-
).map((part) => part.text).join("\n");
|
|
132
|
-
}
|
|
133
|
-
function getTerminalAssistantMessages(messages) {
|
|
134
|
-
let lastNonAssistantIndex = -1;
|
|
135
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
136
|
-
if (!isAssistantMessage(messages[index])) {
|
|
137
|
-
lastNonAssistantIndex = index;
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return messages.slice(lastNonAssistantIndex + 1).filter(isAssistantMessage);
|
|
142
|
-
}
|
|
143
|
-
function trimTrailingAssistantMessages(messages) {
|
|
144
|
-
let end = messages.length;
|
|
145
|
-
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
|
|
146
|
-
end -= 1;
|
|
147
|
-
}
|
|
148
|
-
return end === messages.length ? [...messages] : messages.slice(0, end);
|
|
149
|
-
}
|
|
150
|
-
function hasRuntimeTurnContext(messages) {
|
|
151
|
-
return messages.some(
|
|
152
|
-
(message, index) => isStandaloneRuntimeContextMessage(messages, index) || userMessageContent(message)?.some(isRuntimeTurnContextPart)
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
function retainRuntimeTurnContext(messages) {
|
|
156
|
-
return messages.flatMap((message, index) => {
|
|
157
|
-
if (isStandaloneRuntimeContextMessage(messages, index)) {
|
|
158
|
-
return [message];
|
|
159
|
-
}
|
|
160
|
-
const runtimeContent = userMessageContent(message)?.filter(isRuntimeTurnContextPart) ?? [];
|
|
161
|
-
return runtimeContent.length > 0 ? [{ ...message, content: runtimeContent }] : [];
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
function instructionTextForProjection(text) {
|
|
165
|
-
const withoutContext = text.replace(EMBEDDED_THREAD_CONTEXT_PATTERN, "").trim();
|
|
166
|
-
return unwrapCurrentInstruction(withoutContext) ?? withoutContext;
|
|
167
|
-
}
|
|
168
|
-
function getUserMessageInstructionText(message) {
|
|
169
|
-
if (getPiMessageRole(message) !== "user") {
|
|
170
|
-
return "";
|
|
171
|
-
}
|
|
172
|
-
const content = message.content;
|
|
173
|
-
const text = typeof content === "string" ? content : Array.isArray(content) ? content.flatMap(
|
|
174
|
-
(part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string" ? [part.text] : []
|
|
175
|
-
).join("\n") : "";
|
|
176
|
-
return instructionTextForProjection(text).trim();
|
|
177
|
-
}
|
|
178
|
-
function stripRuntimeTurnContext(messages) {
|
|
179
|
-
return messages.flatMap((message, index) => {
|
|
180
|
-
if (isStandaloneRuntimeContextMessage(messages, index)) {
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
const content = userMessageContent(message);
|
|
184
|
-
if (!content) return [message];
|
|
185
|
-
const nextContent = content.filter(
|
|
186
|
-
(part) => !isRuntimeTurnContextPart(part)
|
|
187
|
-
);
|
|
188
|
-
if (nextContent.length === content.length) return [message];
|
|
189
|
-
if (nextContent.length === 0) return [];
|
|
190
|
-
return [{ ...message, content: nextContent }];
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// src/chat/services/context-budget.ts
|
|
195
|
-
var COMPACTION_TRIGGER_RATIO = 0.9;
|
|
196
|
-
var CONTEXT_INPUT_LIMIT_RATIO = 0.95;
|
|
197
|
-
var COMPACTION_TARGET_RATIO = 0.8;
|
|
198
|
-
var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
|
|
199
|
-
function positiveInteger(value, fallback) {
|
|
200
|
-
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
201
|
-
}
|
|
202
|
-
function estimateTextTokens(text) {
|
|
203
|
-
return Math.ceil(text.length / 4);
|
|
204
|
-
}
|
|
205
|
-
function calculateContextCompactionTriggerTokens(model) {
|
|
206
|
-
const contextWindow = positiveInteger(
|
|
207
|
-
model.contextWindow,
|
|
208
|
-
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
209
|
-
);
|
|
210
|
-
return Math.max(1, Math.floor(contextWindow * COMPACTION_TRIGGER_RATIO));
|
|
211
|
-
}
|
|
212
|
-
function calculateContextInputLimitTokens(model) {
|
|
213
|
-
const contextWindow = positiveInteger(
|
|
214
|
-
model.contextWindow,
|
|
215
|
-
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
216
|
-
);
|
|
217
|
-
return Math.max(1, Math.floor(contextWindow * CONTEXT_INPUT_LIMIT_RATIO));
|
|
218
|
-
}
|
|
219
|
-
function calculateContextCompactionTargetTokens(triggerTokens) {
|
|
220
|
-
return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
|
|
221
|
-
}
|
|
222
|
-
function getModelContextBudget(modelId) {
|
|
223
|
-
const model = resolveGatewayModel(modelId);
|
|
224
|
-
const advertisedContextWindow = positiveInteger(
|
|
225
|
-
model.contextWindow,
|
|
226
|
-
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
227
|
-
);
|
|
228
|
-
return {
|
|
229
|
-
contextWindow: Math.min(
|
|
230
|
-
botConfig.contextWindowTokens,
|
|
231
|
-
advertisedContextWindow
|
|
232
|
-
)
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function getAgentContextCompactionTriggerTokens(modelId) {
|
|
236
|
-
return calculateContextCompactionTriggerTokens(
|
|
237
|
-
getModelContextBudget(modelId)
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
function getAgentContextInputLimitTokens(modelId) {
|
|
241
|
-
return calculateContextInputLimitTokens(getModelContextBudget(modelId));
|
|
242
|
-
}
|
|
243
|
-
function getConversationContextCompactionTriggerTokens() {
|
|
244
|
-
return calculateContextCompactionTriggerTokens(
|
|
245
|
-
getModelContextBudget(botConfig.fastModelId)
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// src/chat/services/conversation-memory.ts
|
|
250
|
-
var CONTEXT_MIN_LIVE_MESSAGES = 12;
|
|
251
|
-
var CONTEXT_COMPACTION_BATCH_SIZE = 24;
|
|
252
|
-
var CONTEXT_MAX_COMPACTIONS = 16;
|
|
253
|
-
var CONTEXT_MAX_MESSAGE_CHARS = 3200;
|
|
254
|
-
var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
255
|
-
function generateConversationId(prefix) {
|
|
256
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
257
|
-
}
|
|
258
|
-
function normalizeConversationText(text) {
|
|
259
|
-
return text.replace(/\r\n?/g, "\n").trim();
|
|
260
|
-
}
|
|
261
|
-
function buildImageContextSuffix(message, conversation) {
|
|
262
|
-
const byFileId = conversation?.vision.byFileId;
|
|
263
|
-
const imageFileIds = message.meta?.imageFileIds ?? [];
|
|
264
|
-
if (!byFileId || imageFileIds.length === 0) {
|
|
265
|
-
return "";
|
|
266
|
-
}
|
|
267
|
-
const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
|
|
268
|
-
if (summaries.length === 0) {
|
|
269
|
-
return "";
|
|
270
|
-
}
|
|
271
|
-
return ` [image context: ${escapeXml(summaries.join(" | "))}]`;
|
|
272
|
-
}
|
|
273
|
-
function renderConversationMessageLine(message, conversation) {
|
|
274
|
-
const displayName = escapeXml(conversationAuthorDisplayName(message));
|
|
275
|
-
const text = escapeXml(
|
|
276
|
-
message.text.replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS)
|
|
277
|
-
);
|
|
278
|
-
const markers = [];
|
|
279
|
-
if (message.meta?.replied === false) {
|
|
280
|
-
markers.push(
|
|
281
|
-
escapeXml(
|
|
282
|
-
`assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
|
|
283
|
-
)
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
if (message.meta?.explicitMention) {
|
|
287
|
-
markers.push("explicit mention");
|
|
288
|
-
}
|
|
289
|
-
const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
|
|
290
|
-
const imageContext = buildImageContextSuffix(message, conversation);
|
|
291
|
-
return `[${message.role}] ${displayName}: ${text}${imageContext}${markerSuffix}`;
|
|
292
|
-
}
|
|
293
|
-
function conversationAuthorDisplayName(message) {
|
|
294
|
-
const author = message.author;
|
|
295
|
-
const fullName = authorDisplayField(author?.fullName, author?.userId);
|
|
296
|
-
const userName = authorDisplayField(author?.userName, author?.userId);
|
|
297
|
-
return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
|
|
298
|
-
}
|
|
299
|
-
function authorDisplayField(value, userId) {
|
|
300
|
-
if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
|
|
301
|
-
return void 0;
|
|
302
|
-
}
|
|
303
|
-
return value;
|
|
304
|
-
}
|
|
305
|
-
function estimateConversationContextTokens(conversation) {
|
|
306
|
-
return estimateTextTokens(buildConversationContext(conversation) ?? "");
|
|
307
|
-
}
|
|
308
|
-
function upsertConversationMessage(conversation, message) {
|
|
309
|
-
const existingIndex = conversation.messages.findIndex(
|
|
310
|
-
(entry) => entry.id === message.id
|
|
311
|
-
);
|
|
312
|
-
if (existingIndex >= 0) {
|
|
313
|
-
conversation.messages[existingIndex] = {
|
|
314
|
-
...conversation.messages[existingIndex],
|
|
315
|
-
...message,
|
|
316
|
-
meta: {
|
|
317
|
-
...conversation.messages[existingIndex]?.meta,
|
|
318
|
-
...message.meta
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
return message.id;
|
|
322
|
-
}
|
|
323
|
-
conversation.messages.push(message);
|
|
324
|
-
return message.id;
|
|
325
|
-
}
|
|
326
|
-
function recordDeliveredAssistantMessage(args) {
|
|
327
|
-
const prefix = `${args.sessionId}:assistant:`;
|
|
328
|
-
const ordinal = args.conversation.messages.filter(
|
|
329
|
-
(message) => message.id.startsWith(prefix)
|
|
330
|
-
).length + 1;
|
|
331
|
-
const messageId = `${prefix}${ordinal}`;
|
|
332
|
-
markConversationMessage(args.conversation, args.userMessageId, {
|
|
333
|
-
replied: true,
|
|
334
|
-
skippedReason: void 0
|
|
335
|
-
});
|
|
336
|
-
upsertConversationMessage(args.conversation, {
|
|
337
|
-
id: messageId,
|
|
338
|
-
role: "assistant",
|
|
339
|
-
text: normalizeConversationText(args.text),
|
|
340
|
-
createdAtMs: Date.now(),
|
|
341
|
-
author: {
|
|
342
|
-
userName: botConfig.userName,
|
|
343
|
-
isBot: true
|
|
344
|
-
},
|
|
345
|
-
meta: {
|
|
346
|
-
replied: true,
|
|
347
|
-
...args.source ? { source: args.source } : void 0
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
return messageId;
|
|
351
|
-
}
|
|
352
|
-
function turnHasReply(conversation, turnId) {
|
|
353
|
-
const assistantPrefix = `${turnId}:assistant:`;
|
|
354
|
-
return conversation.messages.some(
|
|
355
|
-
(message) => message.role === "assistant" && message.id.startsWith(assistantPrefix)
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
function markConversationMessage(conversation, messageId, patch) {
|
|
359
|
-
if (!messageId) return;
|
|
360
|
-
const messageIndex = conversation.messages.findIndex(
|
|
361
|
-
(entry) => entry.id === messageId
|
|
362
|
-
);
|
|
363
|
-
if (messageIndex < 0) return;
|
|
364
|
-
const current = conversation.messages[messageIndex];
|
|
365
|
-
conversation.messages[messageIndex] = {
|
|
366
|
-
...current,
|
|
367
|
-
meta: {
|
|
368
|
-
...current.meta ?? {},
|
|
369
|
-
...patch
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
function buildConversationContext(conversation, options = {}) {
|
|
374
|
-
const messages = conversation.messages.filter(
|
|
375
|
-
(entry) => entry.id !== options.excludeMessageId
|
|
376
|
-
);
|
|
377
|
-
if (messages.length === 0 && conversation.compactions.length === 0) {
|
|
378
|
-
return void 0;
|
|
379
|
-
}
|
|
380
|
-
const lines = [];
|
|
381
|
-
if (conversation.compactions.length > 0) {
|
|
382
|
-
lines.push("<thread-compactions>");
|
|
383
|
-
for (const [index, compaction] of conversation.compactions.entries()) {
|
|
384
|
-
lines.push(
|
|
385
|
-
` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageCount}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
|
|
386
|
-
compaction.summary,
|
|
387
|
-
" </compaction>"
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
lines.push("</thread-compactions>");
|
|
391
|
-
}
|
|
392
|
-
if (messages.length > 0) {
|
|
393
|
-
if (lines.length > 0) {
|
|
394
|
-
lines.push("");
|
|
395
|
-
}
|
|
396
|
-
lines.push('<thread-context authority="evidence-only">');
|
|
397
|
-
for (const [index, message] of messages.entries()) {
|
|
398
|
-
const author = escapeXml(conversationAuthorDisplayName(message));
|
|
399
|
-
const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
|
|
400
|
-
const ts = new Date(message.createdAtMs).toISOString();
|
|
401
|
-
const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
|
|
402
|
-
lines.push(
|
|
403
|
-
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
|
|
404
|
-
renderConversationMessageLine(message, conversation),
|
|
405
|
-
" </message>"
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
lines.push("</thread-context>");
|
|
409
|
-
}
|
|
410
|
-
return lines.join("\n");
|
|
411
|
-
}
|
|
412
|
-
var THREAD_CONTEXT_OPEN = '<thread-context authority="evidence-only">';
|
|
413
|
-
var THREAD_CONTEXT_CLOSE = "</thread-context>";
|
|
414
|
-
function appendThreadContextMessages(baseContext, messageLines) {
|
|
415
|
-
const base = baseContext?.trim();
|
|
416
|
-
if (messageLines.length === 0) {
|
|
417
|
-
return base || void 0;
|
|
418
|
-
}
|
|
419
|
-
const entryBlock = messageLines.join("\n");
|
|
420
|
-
if (!base) {
|
|
421
|
-
return [THREAD_CONTEXT_OPEN, entryBlock, THREAD_CONTEXT_CLOSE].join("\n");
|
|
422
|
-
}
|
|
423
|
-
const closeIdx = base.lastIndexOf(THREAD_CONTEXT_CLOSE);
|
|
424
|
-
if (closeIdx >= 0) {
|
|
425
|
-
const before = base.slice(0, closeIdx).replace(/\s*$/, "");
|
|
426
|
-
const after = base.slice(closeIdx);
|
|
427
|
-
return `${before}
|
|
428
|
-
${entryBlock}
|
|
429
|
-
${after}`;
|
|
430
|
-
}
|
|
431
|
-
return [base, "", THREAD_CONTEXT_OPEN, entryBlock, THREAD_CONTEXT_CLOSE].join(
|
|
432
|
-
"\n"
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
function pruneCompactions(compactions) {
|
|
436
|
-
if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
|
|
437
|
-
return compactions;
|
|
438
|
-
}
|
|
439
|
-
const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
|
|
440
|
-
const merged = compactions.slice(0, overflowCount);
|
|
441
|
-
const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
|
|
442
|
-
const coveredMessageCount = merged.reduce(
|
|
443
|
-
(count, entry) => count + entry.coveredMessageCount,
|
|
444
|
-
0
|
|
445
|
-
);
|
|
446
|
-
const compacted = {
|
|
447
|
-
id: generateConversationId("compaction"),
|
|
448
|
-
createdAtMs: Date.now(),
|
|
449
|
-
summary: mergedSummary,
|
|
450
|
-
coveredMessageCount
|
|
451
|
-
};
|
|
452
|
-
return [compacted, ...compactions.slice(overflowCount)];
|
|
453
|
-
}
|
|
454
|
-
async function summarizeConversationChunk(messages, conversation, context, deps) {
|
|
455
|
-
const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
|
|
456
|
-
try {
|
|
457
|
-
const result = await deps.completeText({
|
|
458
|
-
modelId: botConfig.fastModelId,
|
|
459
|
-
temperature: 0,
|
|
460
|
-
promptName: "junior.thread_compaction",
|
|
461
|
-
messages: [
|
|
462
|
-
{
|
|
463
|
-
role: "user",
|
|
464
|
-
content: [
|
|
465
|
-
"Summarize the following older Slack thread transcript segment for future assistant turns.",
|
|
466
|
-
"Keep the summary factual and concise. Do not invent details.",
|
|
467
|
-
"",
|
|
468
|
-
"Output exactly three XML sections in this order:",
|
|
469
|
-
"<active-asks> one bullet per outstanding user ask that has not been narrowed, answered, or replaced by a later turn. Omit the section body if none. </active-asks>",
|
|
470
|
-
"<resolved-or-replaced-asks> one bullet per ask that has been replaced, narrowed, answered, or already acted on in this segment. Include the replacement/outcome inline. Omit the section body if none. </resolved-or-replaced-asks>",
|
|
471
|
-
"<facts> one bullet per durable fact useful regardless of scope: names, ids, URLs, decisions, locations, preferences, constraints that remain true. Omit the section body if none. </facts>",
|
|
472
|
-
"",
|
|
473
|
-
"Do not output any text outside the three sections.",
|
|
474
|
-
"",
|
|
475
|
-
transcript
|
|
476
|
-
].join("\n"),
|
|
477
|
-
timestamp: Date.now()
|
|
478
|
-
}
|
|
479
|
-
],
|
|
480
|
-
metadata: {
|
|
481
|
-
modelId: botConfig.fastModelId,
|
|
482
|
-
threadId: context.threadId ?? "",
|
|
483
|
-
channelId: context.channelId ?? "",
|
|
484
|
-
actorId: context.actorId ?? "",
|
|
485
|
-
runId: context.runId ?? ""
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
const summary = result.text.trim();
|
|
489
|
-
if (summary.length > 0) {
|
|
490
|
-
return summary.slice(0, 3500);
|
|
491
|
-
}
|
|
492
|
-
} catch (error) {
|
|
493
|
-
logWarn("conversation.compaction.summary.failed", {
|
|
494
|
-
"exception.message": error instanceof Error ? error.message : String(error),
|
|
495
|
-
"app.compaction_messages_covered": messages.length
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
return transcript.slice(0, 2800);
|
|
499
|
-
}
|
|
500
|
-
function getThreadTitleSourceMessage(conversation) {
|
|
501
|
-
let firstMessage;
|
|
502
|
-
for (const message of conversation.messages) {
|
|
503
|
-
if (!isHumanConversationMessage(message)) {
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
if (!firstMessage) {
|
|
507
|
-
firstMessage = message;
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (message.createdAtMs < firstMessage.createdAtMs) {
|
|
511
|
-
firstMessage = message;
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
|
|
515
|
-
firstMessage = message;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return firstMessage;
|
|
519
|
-
}
|
|
520
|
-
async function compactConversationIfNeededWithDeps(conversation, context, deps) {
|
|
521
|
-
let estimatedTokens = estimateConversationContextTokens(conversation);
|
|
522
|
-
setSpanAttributes({
|
|
523
|
-
"app.context_tokens_estimated": estimatedTokens
|
|
524
|
-
});
|
|
525
|
-
const triggerTokens = getConversationContextCompactionTriggerTokens();
|
|
526
|
-
const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
|
|
527
|
-
while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
|
|
528
|
-
const compactCount = Math.min(
|
|
529
|
-
CONTEXT_COMPACTION_BATCH_SIZE,
|
|
530
|
-
conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
|
|
531
|
-
);
|
|
532
|
-
if (compactCount <= 0) {
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
const compactedChunk = conversation.messages.slice(0, compactCount);
|
|
536
|
-
const summary = await summarizeConversationChunk(
|
|
537
|
-
compactedChunk,
|
|
538
|
-
conversation,
|
|
539
|
-
context,
|
|
540
|
-
deps
|
|
541
|
-
);
|
|
542
|
-
conversation.compactions.push({
|
|
543
|
-
id: generateConversationId("compaction"),
|
|
544
|
-
createdAtMs: Date.now(),
|
|
545
|
-
summary,
|
|
546
|
-
coveredMessageCount: compactedChunk.length
|
|
547
|
-
});
|
|
548
|
-
conversation.compactions = pruneCompactions(conversation.compactions);
|
|
549
|
-
conversation.messages = conversation.messages.slice(compactCount);
|
|
550
|
-
estimatedTokens = estimateConversationContextTokens(conversation);
|
|
551
|
-
setSpanAttributes({
|
|
552
|
-
"app.compaction_messages_covered": compactCount,
|
|
553
|
-
"app.compaction.trigger_tokens": triggerTokens,
|
|
554
|
-
"app.compaction.target_tokens": targetTokens,
|
|
555
|
-
"app.context_tokens_estimated": estimatedTokens
|
|
556
|
-
});
|
|
557
|
-
if (estimatedTokens <= targetTokens) {
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
function createConversationMemoryService(deps) {
|
|
563
|
-
return {
|
|
564
|
-
compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps)
|
|
565
|
-
};
|
|
566
|
-
}
|
|
567
|
-
function isHumanConversationMessage(message) {
|
|
568
|
-
return message.role === "user" && message.author?.isBot !== true;
|
|
569
|
-
}
|
|
570
|
-
function getConversationMessageSlackTs(message) {
|
|
571
|
-
return parseSlackMessageTs(message.meta?.slackTs);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// src/chat/conversations/messages.ts
|
|
575
|
-
import { createHash } from "crypto";
|
|
576
|
-
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
577
|
-
|
|
578
|
-
// src/chat/services/conversation-title.ts
|
|
579
|
-
var inFlightTitles = /* @__PURE__ */ new Map();
|
|
580
|
-
async function ensureConversationTitle(args) {
|
|
581
|
-
const pending = inFlightTitles.get(args.conversationId);
|
|
582
|
-
if (pending) {
|
|
583
|
-
return pending;
|
|
584
|
-
}
|
|
585
|
-
const work = ensureConversationTitleOnce(args).finally(() => {
|
|
586
|
-
inFlightTitles.delete(args.conversationId);
|
|
587
|
-
});
|
|
588
|
-
inFlightTitles.set(args.conversationId, work);
|
|
589
|
-
return work;
|
|
590
|
-
}
|
|
591
|
-
async function ensureConversationTitleOnce(args) {
|
|
592
|
-
if (!args.conversation.messages.some(isHumanConversationMessage)) {
|
|
593
|
-
return void 0;
|
|
594
|
-
}
|
|
595
|
-
const store = args.conversationStore ?? getConversationStore();
|
|
596
|
-
try {
|
|
597
|
-
const stored = await store.get({ conversationId: args.conversationId });
|
|
598
|
-
if (stored?.title?.trim()) {
|
|
599
|
-
return void 0;
|
|
600
|
-
}
|
|
601
|
-
if (stored?.parentConversationId) {
|
|
602
|
-
return void 0;
|
|
603
|
-
}
|
|
604
|
-
const titleSourceMessage = getThreadTitleSourceMessage(args.conversation);
|
|
605
|
-
const sourceText = titleSourceMessage?.text.trim() ?? "";
|
|
606
|
-
if (!titleSourceMessage || !sourceText) {
|
|
607
|
-
return void 0;
|
|
608
|
-
}
|
|
609
|
-
let title;
|
|
610
|
-
try {
|
|
611
|
-
const generated = await generateShortTitle({
|
|
612
|
-
completeText,
|
|
613
|
-
kind: "conversation",
|
|
614
|
-
sourceText
|
|
615
|
-
});
|
|
616
|
-
title = generated ?? fallbackShortTitle(sourceText, "Conversation");
|
|
617
|
-
} catch (error) {
|
|
618
|
-
logWarn("conversation.title.generation.failed", {
|
|
619
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
620
|
-
});
|
|
621
|
-
title = fallbackShortTitle(sourceText, "Conversation");
|
|
622
|
-
}
|
|
623
|
-
const normalized = title.trim();
|
|
624
|
-
if (!normalized) {
|
|
625
|
-
return void 0;
|
|
626
|
-
}
|
|
627
|
-
const activityAtMs = titleSourceMessage.createdAtMs;
|
|
628
|
-
const nowMs = args.nowMs ?? Date.now();
|
|
629
|
-
try {
|
|
630
|
-
await store.recordActivity({
|
|
631
|
-
activityAtMs,
|
|
632
|
-
conversationId: args.conversationId,
|
|
633
|
-
nowMs,
|
|
634
|
-
title: normalized
|
|
635
|
-
});
|
|
636
|
-
} catch (error) {
|
|
637
|
-
logException(error, "conversation.title.persist.failed");
|
|
638
|
-
return void 0;
|
|
639
|
-
}
|
|
640
|
-
return normalized;
|
|
641
|
-
} catch (error) {
|
|
642
|
-
logException(error, "conversation.title.ensure.failed");
|
|
643
|
-
return void 0;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
function scheduleConversationTitle(args) {
|
|
647
|
-
if (!args.conversationId) {
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
void ensureConversationTitle({
|
|
651
|
-
conversation: args.conversation,
|
|
652
|
-
conversationId: args.conversationId
|
|
653
|
-
}).catch((error) => {
|
|
654
|
-
logException(error, "conversation.title.task.failed");
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
async function resolveConversationTitle(args) {
|
|
658
|
-
const pending = inFlightTitles.get(args.conversationId);
|
|
659
|
-
if (pending) {
|
|
660
|
-
const generated = await pending;
|
|
661
|
-
if (generated?.trim()) {
|
|
662
|
-
return generated.trim();
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
const store = args.conversationStore ?? getConversationStore();
|
|
666
|
-
const stored = await store.get({ conversationId: args.conversationId });
|
|
667
|
-
return stored?.title?.trim() || void 0;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// src/chat/conversations/message-summaries.ts
|
|
671
|
-
import { isDeepStrictEqual } from "util";
|
|
672
|
-
function projectConversationMessageSummaries(events) {
|
|
673
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
674
|
-
const data = events[index]?.data;
|
|
675
|
-
if (data?.type === "messages_summarized") {
|
|
676
|
-
return data.compactions;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return [];
|
|
680
|
-
}
|
|
681
|
-
async function persistConversationMessageSummaries(args) {
|
|
682
|
-
const eventStore = getConversationEventStore();
|
|
683
|
-
const history = await eventStore.loadMessageHistory(args.conversationId);
|
|
684
|
-
const existing = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
|
|
685
|
-
if (isDeepStrictEqual(existing, args.conversation.compactions)) {
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
const liveMessageIds = new Set(
|
|
689
|
-
args.conversation.messages.map((message) => message.id)
|
|
690
|
-
);
|
|
691
|
-
const historyFromSeq = history.events.find(
|
|
692
|
-
(event) => event.data.type === "message" && liveMessageIds.has(event.data.messageId)
|
|
693
|
-
)?.seq;
|
|
694
|
-
const nextHistoryFromSeq = historyFromSeq ?? Math.max(history.historyFromSeq, (history.events.at(-1)?.seq ?? -1) + 1);
|
|
695
|
-
await eventStore.append(args.conversationId, [
|
|
696
|
-
{
|
|
697
|
-
data: {
|
|
698
|
-
type: "messages_summarized",
|
|
699
|
-
historyFromSeq: nextHistoryFromSeq,
|
|
700
|
-
compactions: args.conversation.compactions
|
|
701
|
-
},
|
|
702
|
-
createdAtMs: args.conversation.compactions.at(-1)?.createdAtMs ?? Date.now()
|
|
703
|
-
}
|
|
704
|
-
]);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// src/chat/conversations/message-projection.ts
|
|
708
|
-
function isMessageEvent(event) {
|
|
709
|
-
return event.data.type === "message" || event.data.type === "message_updated" || event.data.type === "message_handled";
|
|
710
|
-
}
|
|
711
|
-
function splitMeta(meta) {
|
|
712
|
-
const rest = { ...meta ?? {} };
|
|
713
|
-
const author = rest.author;
|
|
714
|
-
delete rest.author;
|
|
715
|
-
return {
|
|
716
|
-
...author ? { author } : void 0,
|
|
717
|
-
...Object.keys(rest).length > 0 ? { meta: rest } : void 0
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
function missingBaselineError(event) {
|
|
721
|
-
return new Error(
|
|
722
|
-
`Message event ${event.data.type} at seq ${event.seq} references ${event.data.messageId} before message`
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
function projectConversationMessages(history) {
|
|
726
|
-
const byId = /* @__PURE__ */ new Map();
|
|
727
|
-
for (const event of history.events) {
|
|
728
|
-
if (!isMessageEvent(event)) continue;
|
|
729
|
-
const data = event.data;
|
|
730
|
-
const current = byId.get(data.messageId);
|
|
731
|
-
if (data.type === "message" || data.type === "message_updated") {
|
|
732
|
-
const message = {
|
|
733
|
-
id: data.messageId,
|
|
734
|
-
role: data.role,
|
|
735
|
-
text: data.text,
|
|
736
|
-
createdAtMs: event.createdAtMs,
|
|
737
|
-
...splitMeta(data.meta)
|
|
738
|
-
};
|
|
739
|
-
if (data.type === "message" && current)
|
|
740
|
-
throw new Error(
|
|
741
|
-
`Duplicate message event for ${data.messageId} at seq ${event.seq}`
|
|
742
|
-
);
|
|
743
|
-
if (data.type === "message_updated" && !current) {
|
|
744
|
-
if (history.historyFromSeq > 0) continue;
|
|
745
|
-
throw missingBaselineError(event);
|
|
746
|
-
}
|
|
747
|
-
byId.set(data.messageId, {
|
|
748
|
-
message: {
|
|
749
|
-
...message,
|
|
750
|
-
createdAtMs: current?.message.createdAtMs ?? message.createdAtMs
|
|
751
|
-
},
|
|
752
|
-
...current?.repliedAtMs === void 0 ? void 0 : { repliedAtMs: current.repliedAtMs }
|
|
753
|
-
});
|
|
754
|
-
continue;
|
|
755
|
-
}
|
|
756
|
-
if (!current) {
|
|
757
|
-
if (history.historyFromSeq > 0) continue;
|
|
758
|
-
throw missingBaselineError(event);
|
|
759
|
-
}
|
|
760
|
-
current.repliedAtMs ??= event.createdAtMs;
|
|
761
|
-
}
|
|
762
|
-
return [...byId.values()].map(({ message, repliedAtMs }) => {
|
|
763
|
-
if (repliedAtMs === void 0) return message;
|
|
764
|
-
return {
|
|
765
|
-
...message,
|
|
766
|
-
meta: { ...message.meta ?? {}, replied: true }
|
|
767
|
-
};
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// src/chat/conversations/messages.ts
|
|
772
|
-
function toStoredConversationMessage(message) {
|
|
773
|
-
const meta = {};
|
|
774
|
-
if (message.author) meta.author = message.author;
|
|
775
|
-
const { replied, ...restMeta } = message.meta ?? {};
|
|
776
|
-
Object.assign(meta, restMeta);
|
|
777
|
-
if (replied === false) meta.replied = false;
|
|
778
|
-
return {
|
|
779
|
-
messageId: message.id,
|
|
780
|
-
role: message.role,
|
|
781
|
-
text: message.text,
|
|
782
|
-
...Object.keys(meta).length > 0 ? { meta } : void 0,
|
|
783
|
-
createdAtMs: message.createdAtMs
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
function canonicalJson(value) {
|
|
787
|
-
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
788
|
-
if (!value || typeof value !== "object") return value;
|
|
789
|
-
return Object.fromEntries(
|
|
790
|
-
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson(item)])
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
function messageStateKey(message) {
|
|
794
|
-
return createHash("sha256").update(JSON.stringify(canonicalJson(message))).digest("hex").slice(0, 32);
|
|
795
|
-
}
|
|
796
|
-
async function hydrateConversationMessages(args) {
|
|
797
|
-
if (!args.conversationId) {
|
|
798
|
-
args.conversation.messages = [];
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
const history = await getConversationEventStore().loadMessageHistory(
|
|
802
|
-
args.conversationId
|
|
803
|
-
);
|
|
804
|
-
args.conversation.compactions = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
|
|
805
|
-
args.conversation.messages = projectConversationMessages(history);
|
|
806
|
-
}
|
|
807
|
-
async function persistConversationMessages(args) {
|
|
808
|
-
if (!args.conversationId || args.conversation.messages.length === 0) return;
|
|
809
|
-
await appendConversationMessages(getConversationEventStore(), {
|
|
810
|
-
...args,
|
|
811
|
-
conversationId: args.conversationId
|
|
812
|
-
});
|
|
813
|
-
scheduleConversationTitle({
|
|
814
|
-
conversation: args.conversation,
|
|
815
|
-
conversationId: args.conversationId
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
async function appendConversationMessages(eventStore, args) {
|
|
819
|
-
if (args.conversation.messages.length === 0) return;
|
|
820
|
-
const history = await eventStore.loadMessageHistory(args.conversationId);
|
|
821
|
-
const existingMessages = new Map(
|
|
822
|
-
projectConversationMessages(history).map((message) => [
|
|
823
|
-
message.id,
|
|
824
|
-
message
|
|
825
|
-
])
|
|
826
|
-
);
|
|
827
|
-
const handledAtMs = args.repliedAtMs ?? Date.now();
|
|
828
|
-
const events = args.conversation.messages.flatMap((message) => {
|
|
829
|
-
const stored = toStoredConversationMessage(message);
|
|
830
|
-
const existing = existingMessages.get(stored.messageId);
|
|
831
|
-
const changed = existing !== void 0 && !isDeepStrictEqual2(toStoredConversationMessage(existing), stored);
|
|
832
|
-
const messageEvent = existing ? changed ? [
|
|
833
|
-
{
|
|
834
|
-
idempotencyKey: `message:${stored.messageId}:update:${messageStateKey(stored)}`,
|
|
835
|
-
createdAtMs: handledAtMs,
|
|
836
|
-
data: {
|
|
837
|
-
type: "message_updated",
|
|
838
|
-
messageId: stored.messageId,
|
|
839
|
-
role: stored.role,
|
|
840
|
-
text: stored.text,
|
|
841
|
-
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : void 0,
|
|
842
|
-
...stored.meta ? { meta: stored.meta } : void 0
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
] : [] : [
|
|
846
|
-
{
|
|
847
|
-
idempotencyKey: `message:${stored.messageId}`,
|
|
848
|
-
createdAtMs: stored.createdAtMs,
|
|
849
|
-
data: {
|
|
850
|
-
type: "message",
|
|
851
|
-
messageId: stored.messageId,
|
|
852
|
-
role: stored.role,
|
|
853
|
-
text: stored.text,
|
|
854
|
-
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : void 0,
|
|
855
|
-
...stored.meta ? { meta: stored.meta } : void 0
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
];
|
|
859
|
-
return [
|
|
860
|
-
...messageEvent,
|
|
861
|
-
...message.meta?.replied === true ? [
|
|
862
|
-
{
|
|
863
|
-
idempotencyKey: `message:${stored.messageId}:handled`,
|
|
864
|
-
createdAtMs: handledAtMs,
|
|
865
|
-
data: {
|
|
866
|
-
type: "message_handled",
|
|
867
|
-
messageId: stored.messageId
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
] : []
|
|
871
|
-
];
|
|
872
|
-
});
|
|
873
|
-
await eventStore.append(args.conversationId, events);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// src/chat/conversations/projection.ts
|
|
877
|
-
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
878
|
-
|
|
879
|
-
// src/chat/pi/messages.ts
|
|
880
|
-
import { z } from "zod";
|
|
881
|
-
var piMessageSchema = z.object({
|
|
882
|
-
role: z.string()
|
|
883
|
-
}).passthrough().transform((value) => value);
|
|
884
|
-
var piContentMessageSchema = z.object({
|
|
885
|
-
content: z.array(z.unknown()),
|
|
886
|
-
role: z.string().min(1)
|
|
887
|
-
}).passthrough().transform((value) => value);
|
|
888
|
-
|
|
889
|
-
// src/chat/pi/conversation-events.ts
|
|
890
|
-
function authorizationObservationMessage(data, createdAtMs) {
|
|
891
|
-
const label = data.kind === "mcp" ? "MCP authorization" : "Authorization";
|
|
892
|
-
return piMessageSchema.parse({
|
|
893
|
-
role: "user",
|
|
894
|
-
content: [
|
|
895
|
-
{
|
|
896
|
-
type: "text",
|
|
897
|
-
text: `${label} completed for provider "${data.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
898
|
-
}
|
|
899
|
-
],
|
|
900
|
-
timestamp: createdAtMs
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
function durableMessages(message) {
|
|
904
|
-
return stripRuntimeTurnContext([message]);
|
|
905
|
-
}
|
|
906
|
-
function historyItemFromPiMessage(message, provenance) {
|
|
907
|
-
const parsed = piMessageSchema.parse(message);
|
|
908
|
-
const { role, ...fields } = parsed;
|
|
909
|
-
if (role === "user") {
|
|
910
|
-
return agentHistoryItemSchema.parse({
|
|
911
|
-
...fields,
|
|
912
|
-
type: "user_message",
|
|
913
|
-
provenance
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
if (role === "assistant") {
|
|
917
|
-
return agentHistoryItemSchema.parse({
|
|
918
|
-
...fields,
|
|
919
|
-
type: "assistant_message"
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
if (role === "toolResult") {
|
|
923
|
-
return agentHistoryItemSchema.parse({
|
|
924
|
-
...fields,
|
|
925
|
-
type: "tool_result"
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
throw new Error(`Unsupported durable agent message role "${role}"`);
|
|
929
|
-
}
|
|
930
|
-
function piMessageFromHistoryItem(data) {
|
|
931
|
-
if (data.type === "user_message") {
|
|
932
|
-
const { type: _2, provenance: __, ...fields2 } = data;
|
|
933
|
-
return piMessageSchema.parse({ ...fields2, role: "user" });
|
|
934
|
-
}
|
|
935
|
-
if (data.type === "assistant_message") {
|
|
936
|
-
const { type: _2, ...fields2 } = data;
|
|
937
|
-
return piMessageSchema.parse({ ...fields2, role: "assistant" });
|
|
938
|
-
}
|
|
939
|
-
const { type: _, ...fields } = data;
|
|
940
|
-
return piMessageSchema.parse({ ...fields, role: "toolResult" });
|
|
941
|
-
}
|
|
942
|
-
function historyItemProvenance(data) {
|
|
943
|
-
return data.type === "user_message" ? data.provenance : contextProvenance;
|
|
944
|
-
}
|
|
945
|
-
function projectConversationEvents(events, options) {
|
|
946
|
-
const messages = [];
|
|
947
|
-
const provenance = [];
|
|
948
|
-
const seqs = [];
|
|
949
|
-
let modelProfile = options.defaultProfile;
|
|
950
|
-
let replacementSeq;
|
|
951
|
-
for (const event of events) {
|
|
952
|
-
if (options.maxSeq !== void 0 && event.seq > options.maxSeq) break;
|
|
953
|
-
if (event.data.type === "unknown") {
|
|
954
|
-
throw new Error(
|
|
955
|
-
`Unsupported conversation event "${event.data.originalType}" at seq ${event.seq} (schema version ${event.schemaVersion})`
|
|
956
|
-
);
|
|
957
|
-
}
|
|
958
|
-
if (event.data.type === "compaction" || event.data.type === "handoff") {
|
|
959
|
-
modelProfile = event.data.modelProfile;
|
|
960
|
-
replacementSeq = event.seq;
|
|
961
|
-
for (const replacement of event.data.replacementHistory) {
|
|
962
|
-
for (const message of durableMessages(
|
|
963
|
-
piMessageFromHistoryItem(replacement.item)
|
|
964
|
-
)) {
|
|
965
|
-
messages.push(message);
|
|
966
|
-
provenance.push(historyItemProvenance(replacement.item));
|
|
967
|
-
seqs.push(replacement.sourceEventSeq ?? event.seq);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
972
|
-
if (event.data.type === "user_message" || event.data.type === "assistant_message" || event.data.type === "tool_result") {
|
|
973
|
-
for (const message of durableMessages(
|
|
974
|
-
piMessageFromHistoryItem(event.data)
|
|
975
|
-
)) {
|
|
976
|
-
messages.push(message);
|
|
977
|
-
provenance.push(historyItemProvenance(event.data));
|
|
978
|
-
seqs.push(event.seq);
|
|
979
|
-
}
|
|
980
|
-
continue;
|
|
981
|
-
}
|
|
982
|
-
if (event.data.type === "authorization_completed") {
|
|
983
|
-
messages.push(
|
|
984
|
-
authorizationObservationMessage(event.data, event.createdAtMs)
|
|
985
|
-
);
|
|
986
|
-
provenance.push(contextProvenance);
|
|
987
|
-
seqs.push(event.seq);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
return {
|
|
991
|
-
messages,
|
|
992
|
-
provenance,
|
|
993
|
-
seqs,
|
|
994
|
-
modelProfile,
|
|
995
|
-
replacementSeq
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// src/chat/conversations/projection.ts
|
|
1000
|
-
function connectedMcpProvidersFromEvents(events, credentialSubjectId) {
|
|
1001
|
-
const providers = /* @__PURE__ */ new Set();
|
|
1002
|
-
for (const event of events) {
|
|
1003
|
-
if (event.data.type === "mcp_provider_connected" && event.data.credentialSubjectId === credentialSubjectId) {
|
|
1004
|
-
providers.add(event.data.provider);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
1008
|
-
}
|
|
1009
|
-
function isUserMessage(message) {
|
|
1010
|
-
return message.role === "user";
|
|
1011
|
-
}
|
|
1012
|
-
var AgentHistoryBranchError = class extends Error {
|
|
1013
|
-
constructor(conversationId) {
|
|
1014
|
-
super(
|
|
1015
|
-
`Agent history for ${conversationId} changed before its committed boundary`
|
|
1016
|
-
);
|
|
1017
|
-
this.name = "AgentHistoryBranchError";
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
function normalizeDurableMessage(message) {
|
|
1021
|
-
return piMessageSchema.parse(
|
|
1022
|
-
JSON.parse(JSON.stringify(sanitizePostgresJson(message)))
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
1025
|
-
function durableMessageIdentity(message) {
|
|
1026
|
-
const record = message;
|
|
1027
|
-
if (record.role === "toolResult") {
|
|
1028
|
-
return {
|
|
1029
|
-
role: "toolResult",
|
|
1030
|
-
timestamp: record.timestamp,
|
|
1031
|
-
toolCallId: record.toolCallId,
|
|
1032
|
-
toolName: record.toolName,
|
|
1033
|
-
isError: record.isError,
|
|
1034
|
-
content: record.content
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
return {
|
|
1038
|
-
role: record.role,
|
|
1039
|
-
timestamp: record.timestamp,
|
|
1040
|
-
content: record.content
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
function countDurablePrefix(current, next) {
|
|
1044
|
-
if (next.length < current.length) {
|
|
1045
|
-
return countMatchingDeepPrefix(current, next);
|
|
1046
|
-
}
|
|
1047
|
-
for (let index = 0; index < current.length; index += 1) {
|
|
1048
|
-
if (!isDeepStrictEqual3(
|
|
1049
|
-
durableMessageIdentity(current[index]),
|
|
1050
|
-
durableMessageIdentity(next[index])
|
|
1051
|
-
)) {
|
|
1052
|
-
return index;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
return current.length;
|
|
1056
|
-
}
|
|
1057
|
-
function countMatchingDeepPrefix(left, right) {
|
|
1058
|
-
const limit = Math.min(left.length, right.length);
|
|
1059
|
-
for (let index = 0; index < limit; index += 1) {
|
|
1060
|
-
if (!isDeepStrictEqual3(left[index], right[index])) {
|
|
1061
|
-
return index;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
return limit;
|
|
1065
|
-
}
|
|
1066
|
-
function resolveCommitProvenance(args) {
|
|
1067
|
-
if (args.explicitProvenance) {
|
|
1068
|
-
if (args.explicitProvenance.length !== args.nextMessages.length) {
|
|
1069
|
-
throw new Error("commit provenance must align one-to-one with messages");
|
|
1070
|
-
}
|
|
1071
|
-
return args.explicitProvenance;
|
|
1072
|
-
}
|
|
1073
|
-
if (args.existing.provenance.length !== args.existing.messages.length) {
|
|
1074
|
-
throw new Error("committed provenance must align one-to-one with messages");
|
|
1075
|
-
}
|
|
1076
|
-
const matchingPrefix = args.matchingPrefix;
|
|
1077
|
-
const provenance = args.nextMessages.map(
|
|
1078
|
-
(_, index) => index < matchingPrefix ? args.existing.provenance[index] : contextProvenance
|
|
1079
|
-
);
|
|
1080
|
-
if (args.newMessageProvenance) {
|
|
1081
|
-
for (let index = args.nextMessages.length - 1; index >= matchingPrefix; index -= 1) {
|
|
1082
|
-
if (isUserMessage(args.nextMessages[index])) {
|
|
1083
|
-
provenance[index] = args.newMessageProvenance;
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
if (args.trailingMessageProvenance) {
|
|
1089
|
-
if (args.trailingMessageProvenance.length > provenance.length) {
|
|
1090
|
-
throw new Error(
|
|
1091
|
-
"trailing commit provenance cannot exceed committed messages"
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
const newMessageCount = args.nextMessages.length - matchingPrefix;
|
|
1095
|
-
if (args.trailingMessageProvenance.length > newMessageCount) {
|
|
1096
|
-
throw new Error(
|
|
1097
|
-
"trailing commit provenance must align to newly committed messages"
|
|
1098
|
-
);
|
|
1099
|
-
}
|
|
1100
|
-
const start = provenance.length - args.trailingMessageProvenance.length;
|
|
1101
|
-
args.trailingMessageProvenance.forEach((entry, offset) => {
|
|
1102
|
-
provenance[start + offset] = entry;
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
return provenance;
|
|
1106
|
-
}
|
|
1107
|
-
async function loadProjection(args) {
|
|
1108
|
-
const events = await getConversationEventStore().loadCurrentHistory(
|
|
1109
|
-
args.conversationId
|
|
1110
|
-
);
|
|
1111
|
-
return projectConversationEvents(events, {
|
|
1112
|
-
defaultProfile: botConfig.defaultProfile
|
|
1113
|
-
}).messages;
|
|
1114
|
-
}
|
|
1115
|
-
async function loadConversationProjection(args) {
|
|
1116
|
-
const events = await getConversationEventStore().loadCurrentHistory(
|
|
1117
|
-
args.conversationId
|
|
1118
|
-
);
|
|
1119
|
-
return projectConversationEvents(events, {
|
|
1120
|
-
defaultProfile: botConfig.defaultProfile
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1123
|
-
async function openConversationProjection(args) {
|
|
1124
|
-
const events = await getConversationEventStore().loadCurrentHistory(
|
|
1125
|
-
args.conversationId
|
|
1126
|
-
);
|
|
1127
|
-
return projectConversationEvents(events, {
|
|
1128
|
-
defaultProfile: botConfig.defaultProfile
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
async function loadTurnProjection(args) {
|
|
1132
|
-
const eventStore = getConversationEventStore();
|
|
1133
|
-
if (args.committedSeq < 0) {
|
|
1134
|
-
return projectConversationEvents(
|
|
1135
|
-
await eventStore.loadCurrentHistory(args.conversationId),
|
|
1136
|
-
{ defaultProfile: botConfig.defaultProfile }
|
|
1137
|
-
);
|
|
1138
|
-
}
|
|
1139
|
-
const historyEvents = await eventStore.loadHistoryContaining(
|
|
1140
|
-
args.conversationId,
|
|
1141
|
-
args.committedSeq,
|
|
1142
|
-
args.includeTail ? void 0 : args.committedSeq
|
|
1143
|
-
);
|
|
1144
|
-
if (!historyEvents) {
|
|
1145
|
-
return void 0;
|
|
1146
|
-
}
|
|
1147
|
-
return projectConversationEvents(historyEvents, {
|
|
1148
|
-
defaultProfile: botConfig.defaultProfile
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
async function loadConnectedMcpProviders(args) {
|
|
1152
|
-
const events = await getConversationEventStore().loadHistory(
|
|
1153
|
-
args.conversationId
|
|
1154
|
-
);
|
|
1155
|
-
return connectedMcpProvidersFromEvents(events, args.credentialSubjectId);
|
|
1156
|
-
}
|
|
1157
|
-
function messageTimestamp2(message) {
|
|
1158
|
-
const timestamp = message.timestamp;
|
|
1159
|
-
return typeof timestamp === "number" ? timestamp : Date.now();
|
|
1160
|
-
}
|
|
1161
|
-
async function commitMessages(args) {
|
|
1162
|
-
const executor = args.executor ?? getSqlExecutor();
|
|
1163
|
-
return await withConversationEventLock(
|
|
1164
|
-
executor,
|
|
1165
|
-
args.conversationId,
|
|
1166
|
-
async () => executor.transaction(
|
|
1167
|
-
async () => await commitMessagesLocked(args, executor)
|
|
1168
|
-
)
|
|
1169
|
-
);
|
|
1170
|
-
}
|
|
1171
|
-
async function commitAcceptedReply(args) {
|
|
1172
|
-
const executor = getSqlExecutor();
|
|
1173
|
-
await withConversationEventLock(
|
|
1174
|
-
executor,
|
|
1175
|
-
args.conversationId,
|
|
1176
|
-
async () => executor.transaction(async () => {
|
|
1177
|
-
if (args.agentMessage) {
|
|
1178
|
-
const agentMessage = normalizeDurableMessage(args.agentMessage);
|
|
1179
|
-
await createSqlConversationEventStore(executor).append(
|
|
1180
|
-
args.conversationId,
|
|
1181
|
-
[
|
|
1182
|
-
{
|
|
1183
|
-
idempotencyKey: `message:${args.conversationMessageId}:agent`,
|
|
1184
|
-
data: historyItemFromPiMessage(agentMessage, contextProvenance),
|
|
1185
|
-
createdAtMs: messageTimestamp2(agentMessage)
|
|
1186
|
-
}
|
|
1187
|
-
]
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
await appendConversationMessages(
|
|
1191
|
-
createSqlConversationEventStore(executor),
|
|
1192
|
-
{
|
|
1193
|
-
conversation: args.conversation,
|
|
1194
|
-
conversationId: args.conversationId,
|
|
1195
|
-
...args.repliedAtMs === void 0 ? void 0 : { repliedAtMs: args.repliedAtMs }
|
|
1196
|
-
}
|
|
1197
|
-
);
|
|
1198
|
-
for (const binding of args.providerConversationBindings ?? []) {
|
|
1199
|
-
await bindProviderConversation(executor, {
|
|
1200
|
-
conversationId: args.conversationId,
|
|
1201
|
-
...binding
|
|
1202
|
-
});
|
|
1203
|
-
}
|
|
1204
|
-
})
|
|
1205
|
-
);
|
|
1206
|
-
}
|
|
1207
|
-
async function commitMessagesLocked(args, executor) {
|
|
1208
|
-
const eventStore = createSqlConversationEventStore(executor);
|
|
1209
|
-
const currentEvents = await eventStore.loadCurrentHistory(
|
|
1210
|
-
args.conversationId
|
|
1211
|
-
);
|
|
1212
|
-
const current = projectConversationEvents(currentEvents, {
|
|
1213
|
-
defaultProfile: botConfig.defaultProfile
|
|
1214
|
-
});
|
|
1215
|
-
const nextLocalMessages = stripRuntimeTurnContext(args.messages).map(
|
|
1216
|
-
normalizeDurableMessage
|
|
1217
|
-
);
|
|
1218
|
-
const matchingPrefix = countDurablePrefix(
|
|
1219
|
-
current.messages,
|
|
1220
|
-
nextLocalMessages
|
|
1221
|
-
);
|
|
1222
|
-
const nextLocalProvenance = resolveCommitProvenance({
|
|
1223
|
-
existing: current,
|
|
1224
|
-
nextMessages: nextLocalMessages,
|
|
1225
|
-
matchingPrefix,
|
|
1226
|
-
...args.provenance ? { explicitProvenance: args.provenance } : void 0,
|
|
1227
|
-
...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : void 0,
|
|
1228
|
-
...args.newMessageProvenance ? { newMessageProvenance: args.newMessageProvenance } : void 0
|
|
1229
|
-
});
|
|
1230
|
-
if (matchingPrefix === current.messages.length) {
|
|
1231
|
-
const newMessages = nextLocalMessages.slice(matchingPrefix);
|
|
1232
|
-
const turnContext = args.turnContext;
|
|
1233
|
-
const turnContextEvents = turnContext?.contexts.map((context, index) => ({
|
|
1234
|
-
idempotencyKey: `turn:${turnContext.turnId}:context:${context.pluginName}:${index}`,
|
|
1235
|
-
createdAtMs: context.loadedAtMs,
|
|
1236
|
-
data: {
|
|
1237
|
-
type: "turn_context",
|
|
1238
|
-
turnId: turnContext.turnId,
|
|
1239
|
-
pluginName: context.pluginName,
|
|
1240
|
-
kind: context.kind,
|
|
1241
|
-
version: context.version,
|
|
1242
|
-
content: context.content
|
|
1243
|
-
}
|
|
1244
|
-
})) ?? [];
|
|
1245
|
-
const appendedEvents = await eventStore.append(args.conversationId, [
|
|
1246
|
-
...newMessages.map((message, index) => ({
|
|
1247
|
-
data: historyItemFromPiMessage(
|
|
1248
|
-
message,
|
|
1249
|
-
nextLocalProvenance[matchingPrefix + index]
|
|
1250
|
-
),
|
|
1251
|
-
createdAtMs: messageTimestamp2(message)
|
|
1252
|
-
})),
|
|
1253
|
-
...turnContextEvents
|
|
1254
|
-
]);
|
|
1255
|
-
const cursor = appendedEvents.at(-1) ?? currentEvents.at(-1);
|
|
1256
|
-
return {
|
|
1257
|
-
committedSeq: cursor?.seq ?? -1,
|
|
1258
|
-
historyVersion: cursor?.historyVersion ?? 0,
|
|
1259
|
-
messageSeqs: [
|
|
1260
|
-
...current.seqs,
|
|
1261
|
-
...appendedEvents.slice(0, newMessages.length).map((event) => event.seq)
|
|
1262
|
-
],
|
|
1263
|
-
messages: nextLocalMessages,
|
|
1264
|
-
provenance: nextLocalProvenance
|
|
1265
|
-
};
|
|
1266
|
-
}
|
|
1267
|
-
throw new AgentHistoryBranchError(args.conversationId);
|
|
1268
|
-
}
|
|
1269
|
-
async function recordMcpProviderConnected(args) {
|
|
1270
|
-
const eventStore = getConversationEventStore();
|
|
1271
|
-
const events = await eventStore.loadCurrentHistory(args.conversationId);
|
|
1272
|
-
if (connectedMcpProvidersFromEvents(events, args.credentialSubjectId).includes(
|
|
1273
|
-
args.provider
|
|
1274
|
-
)) {
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
await eventStore.append(args.conversationId, [
|
|
1278
|
-
{
|
|
1279
|
-
data: {
|
|
1280
|
-
type: "mcp_provider_connected",
|
|
1281
|
-
provider: args.provider,
|
|
1282
|
-
credentialSubjectId: args.credentialSubjectId
|
|
1283
|
-
},
|
|
1284
|
-
createdAtMs: Date.now()
|
|
1285
|
-
}
|
|
1286
|
-
]);
|
|
1287
|
-
}
|
|
1288
|
-
async function recordAuthorizationRequested(args) {
|
|
1289
|
-
const eventStore = getConversationEventStore();
|
|
1290
|
-
const events = await eventStore.loadCurrentHistory(args.conversationId);
|
|
1291
|
-
if (events.some(
|
|
1292
|
-
(event) => event.data.type === "authorization_requested" && event.data.authorizationId === args.authorizationId
|
|
1293
|
-
)) {
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
await eventStore.append(args.conversationId, [
|
|
1297
|
-
{
|
|
1298
|
-
data: {
|
|
1299
|
-
type: "authorization_requested",
|
|
1300
|
-
kind: args.kind,
|
|
1301
|
-
provider: args.provider,
|
|
1302
|
-
actorId: args.actorId,
|
|
1303
|
-
authorizationId: args.authorizationId,
|
|
1304
|
-
delivery: args.delivery
|
|
1305
|
-
},
|
|
1306
|
-
createdAtMs: Date.now()
|
|
1307
|
-
}
|
|
1308
|
-
]);
|
|
1309
|
-
}
|
|
1310
|
-
async function recordAuthorizationCompleted(args) {
|
|
1311
|
-
const eventStore = getConversationEventStore();
|
|
1312
|
-
const events = await eventStore.loadCurrentHistory(args.conversationId);
|
|
1313
|
-
if (events.some(
|
|
1314
|
-
(event) => event.data.type === "authorization_completed" && event.data.authorizationId === args.authorizationId
|
|
1315
|
-
)) {
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
await eventStore.append(args.conversationId, [
|
|
1319
|
-
{
|
|
1320
|
-
data: {
|
|
1321
|
-
type: "authorization_completed",
|
|
1322
|
-
kind: args.kind,
|
|
1323
|
-
provider: args.provider,
|
|
1324
|
-
actorId: args.actorId,
|
|
1325
|
-
authorizationId: args.authorizationId
|
|
1326
|
-
},
|
|
1327
|
-
createdAtMs: Date.now()
|
|
1328
|
-
}
|
|
1329
|
-
]);
|
|
1330
|
-
}
|
|
1331
|
-
function authenticationEventIdempotencyKey(args) {
|
|
1332
|
-
if (args.authorizationId) {
|
|
1333
|
-
return `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:authentication_${args.action}:authorization:${args.authorizationId}`;
|
|
1334
|
-
}
|
|
1335
|
-
return `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:authentication_${args.action}:${args.provider}:actor:${args.actorId}`;
|
|
1336
|
-
}
|
|
1337
|
-
async function recordAuthenticationAccountChange(action, args) {
|
|
1338
|
-
const definition = action === "linked" ? authenticationLinkedEvent : authenticationUnlinkedEvent;
|
|
1339
|
-
const content = definition.parse({
|
|
1340
|
-
actorId: args.actorId,
|
|
1341
|
-
provider: args.provider,
|
|
1342
|
-
...args.accountLabel ? { accountLabel: args.accountLabel } : void 0,
|
|
1343
|
-
...args.authorizationId ? { authorizationId: args.authorizationId } : void 0,
|
|
1344
|
-
...args.providerLabel ? { providerLabel: args.providerLabel } : void 0
|
|
1345
|
-
});
|
|
1346
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
1347
|
-
{
|
|
1348
|
-
createdAtMs: Date.now(),
|
|
1349
|
-
idempotencyKey: authenticationEventIdempotencyKey({
|
|
1350
|
-
action,
|
|
1351
|
-
actorId: args.actorId,
|
|
1352
|
-
authorizationId: args.authorizationId,
|
|
1353
|
-
provider: args.provider
|
|
1354
|
-
}),
|
|
1355
|
-
data: {
|
|
1356
|
-
type: "structured_event",
|
|
1357
|
-
namespace: JUNIOR_NATIVE_EVENT_NAMESPACE,
|
|
1358
|
-
name: definition.eventName,
|
|
1359
|
-
version: definition.version,
|
|
1360
|
-
...args.turnId ? { turnId: args.turnId } : void 0,
|
|
1361
|
-
content
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
]);
|
|
1365
|
-
}
|
|
1366
|
-
async function recordAuthenticationLinked(args) {
|
|
1367
|
-
await recordAuthenticationAccountChange("linked", args);
|
|
1368
|
-
}
|
|
1369
|
-
async function loadAgentsEvent(conversationId) {
|
|
1370
|
-
const event = await getConversationEventStore().loadLatestStructuredEvent(
|
|
1371
|
-
conversationId,
|
|
1372
|
-
JUNIOR_NATIVE_EVENT_NAMESPACE,
|
|
1373
|
-
agentsInstructionsUpdatedEvent.eventName
|
|
1374
|
-
);
|
|
1375
|
-
if (!event || event.data.type !== "structured_event") return void 0;
|
|
1376
|
-
return {
|
|
1377
|
-
content: agentsInstructionsUpdatedEvent.parse(
|
|
1378
|
-
event.data.content
|
|
1379
|
-
),
|
|
1380
|
-
seq: event.seq
|
|
1381
|
-
};
|
|
1382
|
-
}
|
|
1383
|
-
function agentsInstructionsIdempotencyKey(args) {
|
|
1384
|
-
const previous = args.previousSeq ?? "none";
|
|
1385
|
-
return `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:agents_instructions_updated:${args.turnId}:${args.action}:${previous}->${args.fingerprint}`;
|
|
1386
|
-
}
|
|
1387
|
-
async function recordAgentsInstructionsUpdated(args) {
|
|
1388
|
-
const previous = await loadAgentsEvent(args.conversationId);
|
|
1389
|
-
const instructions = args.instructions;
|
|
1390
|
-
if (previous && (!instructions && previous.content.action === "cleared" || instructions && previous.content.action !== "cleared" && previous.content.fingerprint === instructions.fingerprint && previous.content.directory === instructions.directory)) {
|
|
1391
|
-
return;
|
|
1392
|
-
}
|
|
1393
|
-
const action = !instructions ? "cleared" : previous && previous.content.action !== "cleared" ? "replaced" : "loaded";
|
|
1394
|
-
const fingerprint = instructions?.fingerprint ?? "cleared";
|
|
1395
|
-
const content = agentsInstructionsUpdatedEvent.parse({
|
|
1396
|
-
action,
|
|
1397
|
-
fingerprint,
|
|
1398
|
-
sources: instructions?.sources ?? [],
|
|
1399
|
-
...instructions ? { directory: instructions.directory } : void 0,
|
|
1400
|
-
...instructions ? { textBytes: Buffer.byteLength(instructions.text, "utf8") } : void 0
|
|
1401
|
-
});
|
|
1402
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
1403
|
-
{
|
|
1404
|
-
createdAtMs: Date.now(),
|
|
1405
|
-
idempotencyKey: agentsInstructionsIdempotencyKey({
|
|
1406
|
-
action,
|
|
1407
|
-
fingerprint,
|
|
1408
|
-
previousSeq: previous?.seq,
|
|
1409
|
-
turnId: args.turnId
|
|
1410
|
-
}),
|
|
1411
|
-
data: {
|
|
1412
|
-
type: "structured_event",
|
|
1413
|
-
namespace: JUNIOR_NATIVE_EVENT_NAMESPACE,
|
|
1414
|
-
name: agentsInstructionsUpdatedEvent.eventName,
|
|
1415
|
-
version: agentsInstructionsUpdatedEvent.version,
|
|
1416
|
-
turnId: args.turnId,
|
|
1417
|
-
content
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
]);
|
|
1421
|
-
}
|
|
1422
|
-
async function recordSubscribedReplyRoute(args) {
|
|
1423
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
1424
|
-
{
|
|
1425
|
-
idempotencyKey: `native:${JUNIOR_NATIVE_EVENT_NAMESPACE}:passive_reply_routed:${args.runId}`,
|
|
1426
|
-
createdAtMs: Date.now(),
|
|
1427
|
-
data: {
|
|
1428
|
-
type: "structured_event",
|
|
1429
|
-
namespace: JUNIOR_NATIVE_EVENT_NAMESPACE,
|
|
1430
|
-
name: "passive_reply_routed",
|
|
1431
|
-
version: 1,
|
|
1432
|
-
content: {
|
|
1433
|
-
costUsd: args.costUsd,
|
|
1434
|
-
reason: args.reason,
|
|
1435
|
-
shouldReply: args.shouldReply,
|
|
1436
|
-
...args.shouldUnsubscribe !== void 0 ? { shouldUnsubscribe: args.shouldUnsubscribe } : void 0
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
]);
|
|
1441
|
-
}
|
|
1442
|
-
async function loadTurnRoute(args) {
|
|
1443
|
-
const event = await getConversationEventStore().loadByIdempotencyKey(
|
|
1444
|
-
args.conversationId,
|
|
1445
|
-
`turn:${args.turnId}:routed`
|
|
1446
|
-
);
|
|
1447
|
-
if (!event) {
|
|
1448
|
-
return void 0;
|
|
1449
|
-
}
|
|
1450
|
-
if (event.data.type !== "turn_routed" || event.data.turnId !== args.turnId) {
|
|
1451
|
-
throw new Error(`Turn route key for "${args.turnId}" has invalid data`);
|
|
1452
|
-
}
|
|
1453
|
-
return { ...event.data, seq: event.seq };
|
|
1454
|
-
}
|
|
1455
|
-
async function recordTurnRoute(args) {
|
|
1456
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
1457
|
-
{
|
|
1458
|
-
idempotencyKey: `turn:${args.turnId}:routed`,
|
|
1459
|
-
createdAtMs: Date.now(),
|
|
1460
|
-
data: {
|
|
1461
|
-
type: "turn_routed",
|
|
1462
|
-
turnId: args.turnId,
|
|
1463
|
-
modelProfile: args.modelProfile,
|
|
1464
|
-
modelId: args.modelId,
|
|
1465
|
-
...args.costUsd !== void 0 ? { costUsd: args.costUsd } : void 0,
|
|
1466
|
-
reasoningLevel: args.reasoningLevel,
|
|
1467
|
-
...args.confidence !== void 0 ? { confidence: args.confidence } : void 0,
|
|
1468
|
-
source: args.source
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
]);
|
|
1472
|
-
}
|
|
1473
|
-
async function recordToolExecutionStarted(args) {
|
|
1474
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
1475
|
-
{
|
|
1476
|
-
data: {
|
|
1477
|
-
type: "tool_execution_started",
|
|
1478
|
-
toolCallId: args.toolCallId,
|
|
1479
|
-
toolName: args.toolName
|
|
1480
|
-
},
|
|
1481
|
-
createdAtMs: args.createdAtMs ?? Date.now()
|
|
1482
|
-
}
|
|
1483
|
-
]);
|
|
1484
|
-
}
|
|
1485
|
-
function attachmentsDeliveredIdempotencyKey(args) {
|
|
1486
|
-
if (args.toolCallId) {
|
|
1487
|
-
return `attachments_delivered:tool:${args.toolCallId}`;
|
|
1488
|
-
}
|
|
1489
|
-
if (args.turnId) {
|
|
1490
|
-
return `attachments_delivered:turn:${args.turnId}:` + args.attachments.map((attachment) => attachment.id).sort().join(",");
|
|
1491
|
-
}
|
|
1492
|
-
return `attachments_delivered:${args.conversationId}:` + args.attachments.map((attachment) => attachment.id).sort().join(",");
|
|
1493
|
-
}
|
|
1494
|
-
async function recordAttachmentsDelivered(args) {
|
|
1495
|
-
if (args.attachments.length === 0) return;
|
|
1496
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
1497
|
-
{
|
|
1498
|
-
// Retries after a successful Slack upload must not create duplicate rows.
|
|
1499
|
-
idempotencyKey: attachmentsDeliveredIdempotencyKey({
|
|
1500
|
-
attachments: args.attachments,
|
|
1501
|
-
conversationId: args.conversationId,
|
|
1502
|
-
...args.toolCallId ? { toolCallId: args.toolCallId } : void 0,
|
|
1503
|
-
...args.turnId ? { turnId: args.turnId } : void 0
|
|
1504
|
-
}),
|
|
1505
|
-
data: {
|
|
1506
|
-
type: "attachments_delivered",
|
|
1507
|
-
attachments: args.attachments.map((attachment) => ({
|
|
1508
|
-
id: attachment.id,
|
|
1509
|
-
filename: attachment.filename,
|
|
1510
|
-
contentType: attachment.contentType,
|
|
1511
|
-
bytes: attachment.bytes
|
|
1512
|
-
})),
|
|
1513
|
-
...args.toolCallId ? { toolCallId: args.toolCallId } : void 0,
|
|
1514
|
-
...args.turnId ? { turnId: args.turnId } : void 0
|
|
1515
|
-
},
|
|
1516
|
-
createdAtMs: args.createdAtMs ?? Date.now()
|
|
1517
|
-
}
|
|
1518
|
-
]);
|
|
1519
|
-
}
|
|
1520
|
-
async function recordGuardianActionReviewed(args) {
|
|
1521
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
1522
|
-
{
|
|
1523
|
-
data: {
|
|
1524
|
-
type: "guardian_action_reviewed",
|
|
1525
|
-
turnId: args.turnId,
|
|
1526
|
-
toolCallId: args.toolCallId,
|
|
1527
|
-
toolName: args.toolName,
|
|
1528
|
-
...args.costUsd !== void 0 ? { costUsd: args.costUsd } : void 0,
|
|
1529
|
-
decision: args.decision,
|
|
1530
|
-
riskLevel: args.riskLevel,
|
|
1531
|
-
userAuthorization: args.userAuthorization
|
|
1532
|
-
},
|
|
1533
|
-
createdAtMs: Date.now()
|
|
1534
|
-
}
|
|
1535
|
-
]);
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
// src/chat/services/persist-retry.ts
|
|
1539
|
-
var PERSIST_ATTEMPTS = 3;
|
|
1540
|
-
async function persistWithRetry(persist) {
|
|
1541
|
-
let lastError;
|
|
1542
|
-
for (let attempt = 1; attempt <= PERSIST_ATTEMPTS; attempt += 1) {
|
|
1543
|
-
try {
|
|
1544
|
-
await persist();
|
|
1545
|
-
return;
|
|
1546
|
-
} catch (error) {
|
|
1547
|
-
lastError = error;
|
|
1548
|
-
if (attempt < PERSIST_ATTEMPTS) {
|
|
1549
|
-
await sleep(attempt * 100);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
throw lastError;
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
// src/chat/task-execution/turn-cursor.ts
|
|
1557
|
-
import {
|
|
1558
|
-
actorSchema,
|
|
1559
|
-
sourceSchema
|
|
1560
|
-
} from "@sentry/junior-plugin-api";
|
|
1561
|
-
import { z as z2 } from "zod";
|
|
1562
|
-
|
|
1563
|
-
// src/chat/repository-instructions.ts
|
|
1564
|
-
import { createHash as createHash2 } from "crypto";
|
|
1565
|
-
import path2 from "path";
|
|
1566
|
-
|
|
1567
|
-
// src/chat/tools/sandbox/file-utils.ts
|
|
1568
|
-
import path from "path";
|
|
1569
|
-
var MAX_TEXT_CHARS = 6e4;
|
|
1570
|
-
var RIPGREP_EXCLUDED_GLOBS = [
|
|
1571
|
-
"!**/.git/**",
|
|
1572
|
-
"!**/node_modules/**"
|
|
1573
|
-
];
|
|
1574
|
-
var DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
|
|
1575
|
-
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
1576
|
-
function positiveInteger2(value) {
|
|
1577
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1578
|
-
return void 0;
|
|
1579
|
-
}
|
|
1580
|
-
const integer = Math.floor(value);
|
|
1581
|
-
return integer > 0 ? integer : void 0;
|
|
1582
|
-
}
|
|
1583
|
-
function normalizeToLf(value) {
|
|
1584
|
-
return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1585
|
-
}
|
|
1586
|
-
function truncateText(value, maxChars = MAX_TEXT_CHARS) {
|
|
1587
|
-
if (value.length <= maxChars) {
|
|
1588
|
-
return { content: value, truncated: false };
|
|
1589
|
-
}
|
|
1590
|
-
const removed = value.length - maxChars;
|
|
1591
|
-
return {
|
|
1592
|
-
content: `${value.slice(0, maxChars)}
|
|
1593
|
-
|
|
1594
|
-
[output truncated: ${removed} characters removed]`,
|
|
1595
|
-
truncated: true
|
|
1596
|
-
};
|
|
1597
|
-
}
|
|
1598
|
-
function isMissingPathError(error) {
|
|
1599
|
-
if (!error || typeof error !== "object") {
|
|
1600
|
-
return false;
|
|
1601
|
-
}
|
|
1602
|
-
const candidate = error;
|
|
1603
|
-
if (candidate.code === "ENOENT") {
|
|
1604
|
-
return true;
|
|
1605
|
-
}
|
|
1606
|
-
return typeof candidate.message === "string" && candidate.message.startsWith("File not found:");
|
|
1607
|
-
}
|
|
1608
|
-
function missingPathSearchResult(params) {
|
|
1609
|
-
const textPath = params.displayPath ?? params.missingPath ?? params.path;
|
|
1610
|
-
throw new ToolInputError(`Path not found: ${textPath}`);
|
|
1611
|
-
}
|
|
1612
|
-
function escapeRegExp(value) {
|
|
1613
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1614
|
-
}
|
|
1615
|
-
function globToRegExp(pattern) {
|
|
1616
|
-
let source = "";
|
|
1617
|
-
for (let index = 0; index < pattern.length; index += 1) {
|
|
1618
|
-
const char = pattern[index];
|
|
1619
|
-
const next = pattern[index + 1];
|
|
1620
|
-
if (char === "*" && next === "*") {
|
|
1621
|
-
if (pattern[index + 2] === "/") {
|
|
1622
|
-
source += "(?:.*/)?";
|
|
1623
|
-
index += 2;
|
|
1624
|
-
continue;
|
|
1625
|
-
}
|
|
1626
|
-
source += ".*";
|
|
1627
|
-
index += 1;
|
|
1628
|
-
continue;
|
|
1629
|
-
}
|
|
1630
|
-
if (char === "*") {
|
|
1631
|
-
source += "[^/]*";
|
|
1632
|
-
continue;
|
|
1633
|
-
}
|
|
1634
|
-
if (char === "?") {
|
|
1635
|
-
source += "[^/]";
|
|
1636
|
-
continue;
|
|
1637
|
-
}
|
|
1638
|
-
source += escapeRegExp(char);
|
|
1639
|
-
}
|
|
1640
|
-
return new RegExp(`^${source}$`);
|
|
1641
|
-
}
|
|
1642
|
-
function matchesGlob(relativePath, pattern) {
|
|
1643
|
-
const matcher = globToRegExp(pattern);
|
|
1644
|
-
if (matcher.test(relativePath)) {
|
|
1645
|
-
return true;
|
|
1646
|
-
}
|
|
1647
|
-
if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
|
|
1648
|
-
return true;
|
|
1649
|
-
}
|
|
1650
|
-
return !pattern.includes("/") && matcher.test(path.posix.basename(relativePath));
|
|
1651
|
-
}
|
|
1652
|
-
function resolveWorkspacePath(input, fallback = ".") {
|
|
1653
|
-
const requested = (input ?? "").trim() || fallback;
|
|
1654
|
-
const absolute = requested.startsWith("/") ? requested : path.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
|
|
1655
|
-
const normalized = path.posix.normalize(absolute);
|
|
1656
|
-
if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
1657
|
-
throw new ToolInputError(
|
|
1658
|
-
`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
|
|
1659
|
-
);
|
|
1660
|
-
}
|
|
1661
|
-
return normalized;
|
|
1662
|
-
}
|
|
1663
|
-
function getRipgrepSearchLocation(root, rootIsDirectory) {
|
|
1664
|
-
if (rootIsDirectory) {
|
|
1665
|
-
return { cwd: root, target: "." };
|
|
1666
|
-
}
|
|
1667
|
-
return {
|
|
1668
|
-
cwd: path.posix.dirname(root),
|
|
1669
|
-
target: path.posix.basename(root)
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
async function collectFiles(params) {
|
|
1673
|
-
const files = [];
|
|
1674
|
-
let limitReached = false;
|
|
1675
|
-
let missingPath;
|
|
1676
|
-
const visit = async (dirPath) => {
|
|
1677
|
-
if (missingPath) {
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
let entries;
|
|
1681
|
-
try {
|
|
1682
|
-
entries = (await params.fs.readdir(dirPath)).sort(
|
|
1683
|
-
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
1684
|
-
);
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
if (isMissingPathError(error)) {
|
|
1687
|
-
missingPath = dirPath;
|
|
1688
|
-
return;
|
|
1689
|
-
}
|
|
1690
|
-
throw error;
|
|
1691
|
-
}
|
|
1692
|
-
for (const entry of entries) {
|
|
1693
|
-
const fullPath = path.posix.join(dirPath, entry);
|
|
1694
|
-
let stat2;
|
|
1695
|
-
try {
|
|
1696
|
-
stat2 = await params.fs.stat(fullPath);
|
|
1697
|
-
} catch (error) {
|
|
1698
|
-
if (isMissingPathError(error)) {
|
|
1699
|
-
missingPath = fullPath;
|
|
1700
|
-
return;
|
|
1701
|
-
}
|
|
1702
|
-
throw error;
|
|
1703
|
-
}
|
|
1704
|
-
if (stat2.isDirectory()) {
|
|
1705
|
-
if (!SKIPPED_DIRECTORIES.has(entry)) {
|
|
1706
|
-
await visit(fullPath);
|
|
1707
|
-
}
|
|
1708
|
-
if (limitReached) return;
|
|
1709
|
-
continue;
|
|
1710
|
-
}
|
|
1711
|
-
const relativePath = path.posix.relative(params.root, fullPath);
|
|
1712
|
-
if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
|
|
1713
|
-
files.push(fullPath);
|
|
1714
|
-
if (params.limit && files.length >= params.limit) {
|
|
1715
|
-
limitReached = true;
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
};
|
|
1721
|
-
let stat;
|
|
1722
|
-
try {
|
|
1723
|
-
stat = await params.fs.stat(params.root);
|
|
1724
|
-
} catch (error) {
|
|
1725
|
-
if (isMissingPathError(error)) {
|
|
1726
|
-
return {
|
|
1727
|
-
files,
|
|
1728
|
-
limitReached: false,
|
|
1729
|
-
missingPath: params.root,
|
|
1730
|
-
missingRoot: true
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
throw error;
|
|
1734
|
-
}
|
|
1735
|
-
if (!stat.isDirectory()) {
|
|
1736
|
-
const relativePath = path.posix.basename(params.root);
|
|
1737
|
-
return {
|
|
1738
|
-
files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
|
|
1739
|
-
limitReached: false,
|
|
1740
|
-
missingRoot: false
|
|
1741
|
-
};
|
|
1742
|
-
}
|
|
1743
|
-
await visit(params.root);
|
|
1744
|
-
return {
|
|
1745
|
-
files,
|
|
1746
|
-
limitReached,
|
|
1747
|
-
missingPath,
|
|
1748
|
-
missingRoot: missingPath === params.root
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
// src/chat/repository-instructions.ts
|
|
1753
|
-
var AGENTS_FILENAME = "AGENTS.md";
|
|
1754
|
-
var MAX_REPOSITORY_INSTRUCTIONS_BYTES = 32 * 1024;
|
|
1755
|
-
var AGENTS_REPLACEMENT_NOTICE = "These AGENTS.md instructions replace all previously provided AGENTS.md instructions.";
|
|
1756
|
-
var AGENTS_REMOVAL_NOTICE = "The previously provided AGENTS.md instructions no longer apply.";
|
|
1757
|
-
async function pathExists(fs, target) {
|
|
1758
|
-
try {
|
|
1759
|
-
await fs.stat(target);
|
|
1760
|
-
return true;
|
|
1761
|
-
} catch (error) {
|
|
1762
|
-
if (isMissingPathError(error)) {
|
|
1763
|
-
return false;
|
|
1764
|
-
}
|
|
1765
|
-
throw error;
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
async function directoryExists(fs, target) {
|
|
1769
|
-
try {
|
|
1770
|
-
return (await fs.stat(target)).isDirectory();
|
|
1771
|
-
} catch (error) {
|
|
1772
|
-
if (isMissingPathError(error)) {
|
|
1773
|
-
return false;
|
|
1774
|
-
}
|
|
1775
|
-
throw error;
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
async function findGitRoot(fs, cwd) {
|
|
1779
|
-
let current = cwd;
|
|
1780
|
-
for (; ; ) {
|
|
1781
|
-
if (await pathExists(fs, path2.posix.join(current, ".git"))) {
|
|
1782
|
-
return current;
|
|
1783
|
-
}
|
|
1784
|
-
if (current === SANDBOX_WORKSPACE_ROOT) {
|
|
1785
|
-
return void 0;
|
|
1786
|
-
}
|
|
1787
|
-
const parent = path2.posix.dirname(current);
|
|
1788
|
-
if (parent === current || parent !== SANDBOX_WORKSPACE_ROOT && !parent.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
1789
|
-
return void 0;
|
|
1790
|
-
}
|
|
1791
|
-
current = parent;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
async function listRepositoryDirectories(fs) {
|
|
1795
|
-
if (!await directoryExists(fs, SANDBOX_REPOS_ROOT)) {
|
|
1796
|
-
return [];
|
|
1797
|
-
}
|
|
1798
|
-
const entries = await fs.readdir(SANDBOX_REPOS_ROOT);
|
|
1799
|
-
const repositories = [];
|
|
1800
|
-
for (const entry of entries) {
|
|
1801
|
-
const candidate = path2.posix.join(SANDBOX_REPOS_ROOT, entry);
|
|
1802
|
-
if (await directoryExists(fs, candidate) && await pathExists(fs, path2.posix.join(candidate, ".git"))) {
|
|
1803
|
-
repositories.push(candidate);
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
return repositories.sort(
|
|
1807
|
-
(left, right) => left < right ? -1 : left > right ? 1 : 0
|
|
1808
|
-
);
|
|
1809
|
-
}
|
|
1810
|
-
async function readInstructionFile(fs, filePath) {
|
|
1811
|
-
try {
|
|
1812
|
-
const content = await fs.readFile(filePath, { encoding: "utf8" });
|
|
1813
|
-
return content.trim() ? content : void 0;
|
|
1814
|
-
} catch (error) {
|
|
1815
|
-
if (isMissingPathError(error)) {
|
|
1816
|
-
return void 0;
|
|
1817
|
-
}
|
|
1818
|
-
throw error;
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
async function resolveRepositoryInstructions(args) {
|
|
1822
|
-
const gitRoot = await findGitRoot(args.fs, args.cwd);
|
|
1823
|
-
if (!gitRoot) {
|
|
1824
|
-
return void 0;
|
|
1825
|
-
}
|
|
1826
|
-
const directories = [];
|
|
1827
|
-
let current = args.cwd;
|
|
1828
|
-
for (; ; ) {
|
|
1829
|
-
directories.push(current);
|
|
1830
|
-
if (current === gitRoot) {
|
|
1831
|
-
break;
|
|
1832
|
-
}
|
|
1833
|
-
current = path2.posix.dirname(current);
|
|
1834
|
-
}
|
|
1835
|
-
directories.reverse();
|
|
1836
|
-
const sources = [];
|
|
1837
|
-
let remaining = Math.max(
|
|
1838
|
-
0,
|
|
1839
|
-
args.maxBytes ?? MAX_REPOSITORY_INSTRUCTIONS_BYTES
|
|
1840
|
-
);
|
|
1841
|
-
for (const directory of directories) {
|
|
1842
|
-
if (remaining <= 0) {
|
|
1843
|
-
break;
|
|
1844
|
-
}
|
|
1845
|
-
const filePath = path2.posix.join(directory, AGENTS_FILENAME);
|
|
1846
|
-
const content = await readInstructionFile(args.fs, filePath);
|
|
1847
|
-
if (!content) {
|
|
1848
|
-
continue;
|
|
1849
|
-
}
|
|
1850
|
-
const bytes = Buffer.from(content, "utf8");
|
|
1851
|
-
const bounded = bytes.length <= remaining ? content : bytes.subarray(0, remaining).toString("utf8");
|
|
1852
|
-
sources.push({ content: bounded, path: filePath });
|
|
1853
|
-
remaining -= Buffer.byteLength(bounded, "utf8");
|
|
1854
|
-
}
|
|
1855
|
-
if (sources.length === 0) {
|
|
1856
|
-
return void 0;
|
|
1857
|
-
}
|
|
1858
|
-
const text = formatInstructionSources(sources);
|
|
1859
|
-
const fingerprint = createHash2("sha256").update(JSON.stringify({ directory: args.cwd, sources })).digest("hex");
|
|
1860
|
-
return { directory: args.cwd, fingerprint, sources, text };
|
|
1861
|
-
}
|
|
1862
|
-
function formatInstructionSources(sources) {
|
|
1863
|
-
if (sources.length === 1) {
|
|
1864
|
-
return sources[0].content;
|
|
1865
|
-
}
|
|
1866
|
-
return sources.map(
|
|
1867
|
-
(source) => `## ${path2.posix.dirname(source.path)}
|
|
1868
|
-
|
|
1869
|
-
${source.content}`
|
|
1870
|
-
).join("\n\n");
|
|
1871
|
-
}
|
|
1872
|
-
function mergeRepositoryInstructions(bundles) {
|
|
1873
|
-
if (bundles.length === 0) {
|
|
1874
|
-
return void 0;
|
|
1875
|
-
}
|
|
1876
|
-
if (bundles.length === 1) {
|
|
1877
|
-
return bundles[0];
|
|
1878
|
-
}
|
|
1879
|
-
const sources = bundles.flatMap((bundle) => bundle.sources);
|
|
1880
|
-
const text = formatInstructionSources(sources);
|
|
1881
|
-
const fingerprint = createHash2("sha256").update(
|
|
1882
|
-
JSON.stringify({
|
|
1883
|
-
directories: bundles.map((bundle) => bundle.directory ?? null),
|
|
1884
|
-
sources
|
|
1885
|
-
})
|
|
1886
|
-
).digest("hex");
|
|
1887
|
-
return { fingerprint, sources, text };
|
|
1888
|
-
}
|
|
1889
|
-
async function resolveRepositoryInstructionsForDirectories(args) {
|
|
1890
|
-
const uniqueDirectories = [
|
|
1891
|
-
...new Set(
|
|
1892
|
-
args.directories.filter(
|
|
1893
|
-
(directory) => directory === SANDBOX_WORKSPACE_ROOT || directory.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)
|
|
1894
|
-
)
|
|
1895
|
-
)
|
|
1896
|
-
].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
1897
|
-
let remaining = MAX_REPOSITORY_INSTRUCTIONS_BYTES;
|
|
1898
|
-
const bundles = [];
|
|
1899
|
-
for (const directory of uniqueDirectories) {
|
|
1900
|
-
if (remaining <= 0) {
|
|
1901
|
-
break;
|
|
1902
|
-
}
|
|
1903
|
-
const instructions = await resolveRepositoryInstructions({
|
|
1904
|
-
cwd: directory,
|
|
1905
|
-
fs: args.fs,
|
|
1906
|
-
maxBytes: remaining
|
|
1907
|
-
});
|
|
1908
|
-
if (!instructions) {
|
|
1909
|
-
continue;
|
|
1910
|
-
}
|
|
1911
|
-
bundles.push(instructions);
|
|
1912
|
-
remaining -= instructions.sources.reduce(
|
|
1913
|
-
(total, source) => total + Buffer.byteLength(source.content, "utf8"),
|
|
1914
|
-
0
|
|
1915
|
-
);
|
|
1916
|
-
}
|
|
1917
|
-
return mergeRepositoryInstructions(bundles);
|
|
1918
|
-
}
|
|
1919
|
-
function renderAgentsInstructions(args) {
|
|
1920
|
-
const directory = args.directory ? ` for ${args.directory}` : "";
|
|
1921
|
-
return `# AGENTS.md instructions${directory}
|
|
1922
|
-
|
|
1923
|
-
<INSTRUCTIONS>
|
|
1924
|
-
${args.text}
|
|
1925
|
-
</INSTRUCTIONS>`;
|
|
1926
|
-
}
|
|
1927
|
-
function buildAgentsInstructionsMessage(args) {
|
|
1928
|
-
return {
|
|
1929
|
-
role: "user",
|
|
1930
|
-
content: [{ type: "text", text: renderAgentsInstructions(args) }],
|
|
1931
|
-
timestamp: args.timestamp ?? Date.now()
|
|
1932
|
-
};
|
|
1933
|
-
}
|
|
1934
|
-
function findVisibleAgentsInstructions(messages) {
|
|
1935
|
-
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
1936
|
-
const message = messages[messageIndex];
|
|
1937
|
-
if (message.role !== "user" || !Array.isArray(message.content)) {
|
|
1938
|
-
continue;
|
|
1939
|
-
}
|
|
1940
|
-
for (let partIndex = message.content.length - 1; partIndex >= 0; partIndex -= 1) {
|
|
1941
|
-
const part = message.content[partIndex];
|
|
1942
|
-
if (part.type !== "text" || typeof part.text !== "string" || !part.text.startsWith("# AGENTS.md instructions")) {
|
|
1943
|
-
continue;
|
|
1944
|
-
}
|
|
1945
|
-
const match = part.text.match(
|
|
1946
|
-
/^# AGENTS\.md instructions(?: for ([^\n]+))?\n\n<INSTRUCTIONS>\n([\s\S]*)\n<\/INSTRUCTIONS>$/
|
|
1947
|
-
);
|
|
1948
|
-
if (!match) {
|
|
1949
|
-
continue;
|
|
1950
|
-
}
|
|
1951
|
-
let text = match[2];
|
|
1952
|
-
if (text === AGENTS_REMOVAL_NOTICE) {
|
|
1953
|
-
return { active: false };
|
|
1954
|
-
}
|
|
1955
|
-
if (text.startsWith(`${AGENTS_REPLACEMENT_NOTICE}
|
|
1956
|
-
|
|
1957
|
-
`)) {
|
|
1958
|
-
text = text.slice(AGENTS_REPLACEMENT_NOTICE.length + 2);
|
|
1959
|
-
}
|
|
1960
|
-
return {
|
|
1961
|
-
active: true,
|
|
1962
|
-
...match[1] ? { directory: match[1] } : void 0,
|
|
1963
|
-
text
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
return void 0;
|
|
1968
|
-
}
|
|
1969
|
-
function isAgentsInstructionsMessage(message) {
|
|
1970
|
-
return Boolean(findVisibleAgentsInstructions([message]));
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
// src/chat/task-execution/turn-cursor-keys.ts
|
|
1974
|
-
var TURN_CURSOR_PREFIX = "junior:turn_cursor:v2";
|
|
1975
|
-
function turnCursorKey(conversationId, turnId) {
|
|
1976
|
-
return `${TURN_CURSOR_PREFIX}:${conversationId}:${turnId}`;
|
|
1977
|
-
}
|
|
1978
|
-
function turnCursorIndexKey(conversationId) {
|
|
1979
|
-
return `${TURN_CURSOR_PREFIX}:conversation:${conversationId}:index`;
|
|
1980
|
-
}
|
|
1981
|
-
function turnCursorMutationKey(conversationId, turnId) {
|
|
1982
|
-
return `${TURN_CURSOR_PREFIX}:mutation:${conversationId}:${turnId}`;
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
// src/chat/task-execution/turn-cursor.ts
|
|
1986
|
-
var TURN_CURSOR_SCHEMA_VERSION = 2;
|
|
1987
|
-
var TURN_CURSOR_RESUME_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1988
|
-
var TURN_CURSOR_TERMINAL_TTL_MS = 60 * 60 * 1e3;
|
|
1989
|
-
var TURN_CURSOR_MUTATION_LOCK_WAIT_MS = 1e4;
|
|
1990
|
-
async function withTurnCursorMutation(conversationId, turnId, run) {
|
|
1991
|
-
const state = getStateAdapter();
|
|
1992
|
-
await state.connect();
|
|
1993
|
-
const result = await withLock(
|
|
1994
|
-
state,
|
|
1995
|
-
turnCursorMutationKey(conversationId, turnId),
|
|
1996
|
-
async (lock) => await run(async () => await fenceLock(state, lock)),
|
|
1997
|
-
{
|
|
1998
|
-
keepAlive: true,
|
|
1999
|
-
ttlMs: MUTATION_LOCK_TTL_MS,
|
|
2000
|
-
waitMs: TURN_CURSOR_MUTATION_LOCK_WAIT_MS
|
|
2001
|
-
}
|
|
2002
|
-
);
|
|
2003
|
-
if (!result.acquired) {
|
|
2004
|
-
throw new Error(`Could not acquire turn cursor lock for ${turnId}`);
|
|
2005
|
-
}
|
|
2006
|
-
return result.value;
|
|
2007
|
-
}
|
|
2008
|
-
function executionMetricsForTurn(conversation, turnId) {
|
|
2009
|
-
return conversation?.executionMetrics?.runId === turnId ? conversation.executionMetrics : void 0;
|
|
2010
|
-
}
|
|
2011
|
-
function definedProps(values) {
|
|
2012
|
-
return Object.fromEntries(
|
|
2013
|
-
Object.entries(values).filter(([, value]) => value !== void 0)
|
|
2014
|
-
);
|
|
2015
|
-
}
|
|
2016
|
-
var turnStatusSchema = z2.enum([
|
|
2017
|
-
"running",
|
|
2018
|
-
"paused",
|
|
2019
|
-
"completed",
|
|
2020
|
-
"failed",
|
|
2021
|
-
"abandoned"
|
|
2022
|
-
]);
|
|
2023
|
-
var turnSurfaceSchema = z2.enum([
|
|
2024
|
-
"slack",
|
|
2025
|
-
"api",
|
|
2026
|
-
"scheduler",
|
|
2027
|
-
"internal"
|
|
2028
|
-
]);
|
|
2029
|
-
var turnPauseReasonSchema = z2.enum([
|
|
2030
|
-
"timeout",
|
|
2031
|
-
"auth",
|
|
2032
|
-
"yield",
|
|
2033
|
-
"retry"
|
|
2034
|
-
]);
|
|
2035
|
-
var nonNegativeNumberSchema = z2.number().finite().nonnegative();
|
|
2036
|
-
var seqCursorSchema = z2.number().int().min(-1);
|
|
2037
|
-
var storedTurnSummarySchema = z2.object({
|
|
2038
|
-
schemaVersion: z2.literal(TURN_CURSOR_SCHEMA_VERSION),
|
|
2039
|
-
version: z2.number().int().nonnegative(),
|
|
2040
|
-
conversationId: z2.string().min(1),
|
|
2041
|
-
dispatchId: z2.string().min(1).optional(),
|
|
2042
|
-
dispatchOutcome: z2.enum(["blocked", "completed", "failed"]).optional(),
|
|
2043
|
-
resultMessageId: z2.string().min(1).optional(),
|
|
2044
|
-
resumeReason: turnPauseReasonSchema.optional(),
|
|
2045
|
-
turnId: z2.string().min(1),
|
|
2046
|
-
sliceId: z2.number().int().nonnegative(),
|
|
2047
|
-
state: turnStatusSchema,
|
|
2048
|
-
surface: turnSurfaceSchema.optional(),
|
|
2049
|
-
updatedAtMs: nonNegativeNumberSchema
|
|
2050
|
-
}).strip();
|
|
2051
|
-
var storedTurnRecordSchema = z2.object({
|
|
2052
|
-
// TODO(dcramer): Require Actor after no deployed Turn cursor can omit it.
|
|
2053
|
-
actor: actorSchema.optional(),
|
|
2054
|
-
schemaVersion: z2.literal(TURN_CURSOR_SCHEMA_VERSION),
|
|
2055
|
-
version: z2.number().int().nonnegative(),
|
|
2056
|
-
conversationId: z2.string().min(1),
|
|
2057
|
-
dispatchId: z2.string().min(1).optional(),
|
|
2058
|
-
dispatchOutcome: z2.enum(["blocked", "completed", "failed"]).optional(),
|
|
2059
|
-
resultMessageId: z2.string().min(1).optional(),
|
|
2060
|
-
lastProgressAtMs: nonNegativeNumberSchema,
|
|
2061
|
-
resumeReason: turnPauseReasonSchema.optional(),
|
|
2062
|
-
publishExternally: z2.boolean().optional(),
|
|
2063
|
-
source: sourceSchema.optional(),
|
|
2064
|
-
resumedFromSliceId: z2.number().int().nonnegative().optional(),
|
|
2065
|
-
turnId: z2.string().min(1),
|
|
2066
|
-
sliceId: z2.number().int().nonnegative(),
|
|
2067
|
-
startedAtMs: nonNegativeNumberSchema,
|
|
2068
|
-
state: turnStatusSchema,
|
|
2069
|
-
surface: turnSurfaceSchema.optional(),
|
|
2070
|
-
traceId: z2.string().optional(),
|
|
2071
|
-
updatedAtMs: nonNegativeNumberSchema,
|
|
2072
|
-
committedSeq: seqCursorSchema,
|
|
2073
|
-
historyVersion: z2.number().int().nonnegative().optional(),
|
|
2074
|
-
errorMessage: z2.string().optional(),
|
|
2075
|
-
cumulativeToolCallCount: z2.number().int().nonnegative().optional(),
|
|
2076
|
-
turnStartSeq: seqCursorSchema.optional(),
|
|
2077
|
-
runtimeContext: z2.array(piMessageSchema).optional()
|
|
2078
|
-
}).strip();
|
|
2079
|
-
function conversationExecutionFromSummary(summary) {
|
|
2080
|
-
const status = summary.state === "completed" || summary.state === "abandoned" ? "idle" : summary.state;
|
|
2081
|
-
return {
|
|
2082
|
-
status,
|
|
2083
|
-
runId: summary.turnId,
|
|
2084
|
-
updatedAtMs: summary.updatedAtMs
|
|
2085
|
-
};
|
|
2086
|
-
}
|
|
2087
|
-
function sessionLogActor(actor) {
|
|
2088
|
-
return actor?.platform === "slack" ? toStoredSlackActor(actor) : void 0;
|
|
2089
|
-
}
|
|
2090
|
-
function parseTurnRecord(value) {
|
|
2091
|
-
return storedTurnRecordSchema.parse(value);
|
|
2092
|
-
}
|
|
2093
|
-
function parseTurnSummary(value) {
|
|
2094
|
-
return storedTurnSummarySchema.parse(value);
|
|
2095
|
-
}
|
|
2096
|
-
function storeTurnSummary(summary) {
|
|
2097
|
-
return storedTurnSummarySchema.parse(summary);
|
|
2098
|
-
}
|
|
2099
|
-
function turnCursorTtlMs(state, ttlMs) {
|
|
2100
|
-
if (ttlMs !== void 0) {
|
|
2101
|
-
return Math.max(1, ttlMs);
|
|
2102
|
-
}
|
|
2103
|
-
return state === "running" || state === "paused" ? TURN_CURSOR_RESUME_TTL_MS : TURN_CURSOR_TERMINAL_TTL_MS;
|
|
2104
|
-
}
|
|
2105
|
-
function turnCursorIndexTtlMs(ttlMs) {
|
|
2106
|
-
return Math.max(1, ttlMs ?? TURN_CURSOR_RESUME_TTL_MS);
|
|
2107
|
-
}
|
|
2108
|
-
function projectTurnSummary(record) {
|
|
2109
|
-
return parseTurnSummary(record);
|
|
2110
|
-
}
|
|
2111
|
-
async function appendTurnSummary(summary, ttlMs) {
|
|
2112
|
-
const stateAdapter = getStateAdapter();
|
|
2113
|
-
await stateAdapter.appendToList(
|
|
2114
|
-
turnCursorIndexKey(summary.conversationId),
|
|
2115
|
-
storeTurnSummary(summary),
|
|
2116
|
-
{ ttlMs }
|
|
2117
|
-
);
|
|
2118
|
-
}
|
|
2119
|
-
async function recordConversationActivityMetadata(args) {
|
|
2120
|
-
const conversationStore = args.conversationStore ?? getConversationStore();
|
|
2121
|
-
const conversation = await conversationStore.get({
|
|
2122
|
-
conversationId: args.summary.conversationId
|
|
2123
|
-
});
|
|
2124
|
-
const hasParent = Boolean(conversation?.parentConversationId);
|
|
2125
|
-
const destination = hasParent ? void 0 : args.destination;
|
|
2126
|
-
if (!hasParent && !destination && !conversation) {
|
|
2127
|
-
return;
|
|
2128
|
-
}
|
|
2129
|
-
const activitySource = hasParent ? "internal" : args.source?.kind === "web" ? "web" : destination?.platform === "local" ? "local" : destination ? args.summary.surface : void 0;
|
|
2130
|
-
await conversationStore.recordActivity({
|
|
2131
|
-
activityAtMs: args.summary.updatedAtMs,
|
|
2132
|
-
channelName: args.summary.channelName,
|
|
2133
|
-
conversationId: args.summary.conversationId,
|
|
2134
|
-
destination,
|
|
2135
|
-
nowMs: args.nowMs,
|
|
2136
|
-
actor: sessionLogActor(args.actor),
|
|
2137
|
-
...definedProps({ source: activitySource }),
|
|
2138
|
-
...args.source ? { sessionSource: args.source } : void 0,
|
|
2139
|
-
visibility: hasParent ? void 0 : args.destinationVisibility
|
|
2140
|
-
});
|
|
2141
|
-
await conversationStore.recordExecution({
|
|
2142
|
-
channelName: args.summary.channelName,
|
|
2143
|
-
conversationId: args.summary.conversationId,
|
|
2144
|
-
createdAtMs: args.summary.startedAtMs,
|
|
2145
|
-
destination,
|
|
2146
|
-
execution: conversationExecutionFromSummary(args.summary),
|
|
2147
|
-
lastActivityAtMs: args.summary.updatedAtMs,
|
|
2148
|
-
metrics: {
|
|
2149
|
-
durationMs: args.summary.cumulativeDurationMs,
|
|
2150
|
-
...args.summary.cumulativeUsage ? { usage: args.summary.cumulativeUsage } : void 0
|
|
2151
|
-
},
|
|
2152
|
-
actor: sessionLogActor(args.actor),
|
|
2153
|
-
...definedProps({ source: activitySource }),
|
|
2154
|
-
updatedAtMs: args.nowMs,
|
|
2155
|
-
visibility: hasParent ? void 0 : args.destinationVisibility
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
function materializeTurnRecord(stored, piProjection, turnStartMessageIndex, restoreVolatileContext = true, runtimeMetrics) {
|
|
2159
|
-
const restoredProjection = restoreVolatileContext && (stored.state === "running" || stored.state === "paused") ? restoreRuntimeContext(piProjection, stored.runtimeContext) : piProjection;
|
|
2160
|
-
return {
|
|
2161
|
-
...definedProps({ actor: stored.actor }),
|
|
2162
|
-
schemaVersion: stored.schemaVersion,
|
|
2163
|
-
version: stored.version,
|
|
2164
|
-
conversationId: stored.conversationId,
|
|
2165
|
-
turnId: stored.turnId,
|
|
2166
|
-
sliceId: stored.sliceId,
|
|
2167
|
-
state: stored.state,
|
|
2168
|
-
startedAtMs: stored.startedAtMs,
|
|
2169
|
-
lastProgressAtMs: stored.lastProgressAtMs,
|
|
2170
|
-
updatedAtMs: stored.updatedAtMs,
|
|
2171
|
-
piMessages: restoredProjection.messages,
|
|
2172
|
-
piMessageProvenance: restoredProjection.provenance,
|
|
2173
|
-
actors: instructionActors(piProjection.provenance),
|
|
2174
|
-
cumulativeDurationMs: runtimeMetrics?.cumulativeDurationMs ?? 0,
|
|
2175
|
-
...definedProps({
|
|
2176
|
-
channelName: runtimeMetrics?.channelName,
|
|
2177
|
-
cumulativeToolCallCount: stored.cumulativeToolCallCount,
|
|
2178
|
-
cumulativeUsage: runtimeMetrics?.cumulativeUsage,
|
|
2179
|
-
dispatchId: stored.dispatchId,
|
|
2180
|
-
dispatchOutcome: stored.dispatchOutcome,
|
|
2181
|
-
errorMessage: stored.errorMessage,
|
|
2182
|
-
resumeReason: stored.resumeReason,
|
|
2183
|
-
source: stored.source,
|
|
2184
|
-
resultMessageId: stored.resultMessageId,
|
|
2185
|
-
resumedFromSliceId: stored.resumedFromSliceId,
|
|
2186
|
-
surface: stored.surface,
|
|
2187
|
-
traceId: stored.traceId,
|
|
2188
|
-
turnStartMessageIndex
|
|
2189
|
-
})
|
|
2190
|
-
};
|
|
2191
|
-
}
|
|
2192
|
-
function restoreRuntimeContext(projection, runtimeContext) {
|
|
2193
|
-
if (!runtimeContext || runtimeContext.length === 0) return projection;
|
|
2194
|
-
const restoredMessages = [...projection.messages];
|
|
2195
|
-
const restoredProvenance = [...projection.provenance];
|
|
2196
|
-
const unmatchedRuntimeContext = [];
|
|
2197
|
-
for (const runtimeMessage of runtimeContext) {
|
|
2198
|
-
const runtime = runtimeMessage;
|
|
2199
|
-
const targetIndex = restoredMessages.findIndex((message) => {
|
|
2200
|
-
const candidate = message;
|
|
2201
|
-
return candidate.role === "user" && candidate.timestamp === runtime.timestamp;
|
|
2202
|
-
});
|
|
2203
|
-
if (targetIndex < 0) {
|
|
2204
|
-
if (isAgentsInstructionsMessage(runtimeMessage)) {
|
|
2205
|
-
const followingIndex = restoredMessages.findIndex((message) => {
|
|
2206
|
-
const timestamp = message.timestamp;
|
|
2207
|
-
return typeof runtime.timestamp === "number" && typeof timestamp === "number" && timestamp > runtime.timestamp;
|
|
2208
|
-
});
|
|
2209
|
-
const insertionIndex = followingIndex < 0 ? restoredMessages.length : followingIndex;
|
|
2210
|
-
restoredMessages.splice(insertionIndex, 0, runtimeMessage);
|
|
2211
|
-
restoredProvenance.splice(insertionIndex, 0, contextProvenance);
|
|
2212
|
-
} else {
|
|
2213
|
-
unmatchedRuntimeContext.push(runtimeMessage);
|
|
2214
|
-
}
|
|
2215
|
-
continue;
|
|
2216
|
-
}
|
|
2217
|
-
restoredMessages.splice(targetIndex, 0, runtimeMessage);
|
|
2218
|
-
restoredProvenance.splice(targetIndex, 0, contextProvenance);
|
|
2219
|
-
}
|
|
2220
|
-
return {
|
|
2221
|
-
messages: [...unmatchedRuntimeContext, ...restoredMessages],
|
|
2222
|
-
provenance: [
|
|
2223
|
-
...unmatchedRuntimeContext.map(() => contextProvenance),
|
|
2224
|
-
...restoredProvenance
|
|
2225
|
-
]
|
|
2226
|
-
};
|
|
2227
|
-
}
|
|
2228
|
-
async function getStoredTurnRecord(conversationId, turnId) {
|
|
2229
|
-
const stateAdapter = getStateAdapter();
|
|
2230
|
-
await stateAdapter.connect();
|
|
2231
|
-
const value = await stateAdapter.get(turnCursorKey(conversationId, turnId));
|
|
2232
|
-
return value == null ? void 0 : parseTurnRecord(value);
|
|
2233
|
-
}
|
|
2234
|
-
async function materializeStoredTurnRecord(conversationId, turnId, followCurrentReplacement) {
|
|
2235
|
-
const parsed = await getStoredTurnRecord(conversationId, turnId);
|
|
2236
|
-
if (!parsed) {
|
|
2237
|
-
return void 0;
|
|
2238
|
-
}
|
|
2239
|
-
const pinnedProjection = await loadTurnProjection({
|
|
2240
|
-
conversationId,
|
|
2241
|
-
committedSeq: parsed.committedSeq,
|
|
2242
|
-
// Unfinished records include the current-epoch tail so parked input
|
|
2243
|
-
// appended after the last safe boundary stays model-visible on resume.
|
|
2244
|
-
includeTail: parsed.state === "running" || parsed.state === "paused"
|
|
2245
|
-
});
|
|
2246
|
-
if (!pinnedProjection) {
|
|
2247
|
-
return void 0;
|
|
2248
|
-
}
|
|
2249
|
-
const eventStore = getConversationEventStore();
|
|
2250
|
-
const currentHistoryVersion = await eventStore.loadCurrentHistoryVersion(conversationId);
|
|
2251
|
-
const followsReplacement = followCurrentReplacement && (parsed.state === "running" || parsed.state === "paused") && parsed.historyVersion !== void 0 && parsed.historyVersion !== currentHistoryVersion;
|
|
2252
|
-
const piProjection = followsReplacement ? projectConversationEvents(
|
|
2253
|
-
await eventStore.loadCurrentHistory(conversationId),
|
|
2254
|
-
{ defaultProfile: botConfig.defaultProfile }
|
|
2255
|
-
) : pinnedProjection;
|
|
2256
|
-
const turnStartMessageIndex = followsReplacement ? 0 : parsed.turnStartSeq === void 0 ? void 0 : piProjection.seqs.filter((seq) => seq <= parsed.turnStartSeq).length;
|
|
2257
|
-
const conversation = await getConversationStore().get({ conversationId });
|
|
2258
|
-
const executionMetrics = executionMetricsForTurn(conversation, turnId);
|
|
2259
|
-
return materializeTurnRecord(
|
|
2260
|
-
parsed,
|
|
2261
|
-
piProjection,
|
|
2262
|
-
turnStartMessageIndex,
|
|
2263
|
-
!followsReplacement && (parsed.historyVersion === void 0 || parsed.historyVersion === currentHistoryVersion),
|
|
2264
|
-
{
|
|
2265
|
-
channelName: conversation?.channelName,
|
|
2266
|
-
cumulativeDurationMs: executionMetrics?.durationMs,
|
|
2267
|
-
cumulativeUsage: executionMetrics?.usage
|
|
2268
|
-
}
|
|
2269
|
-
);
|
|
2270
|
-
}
|
|
2271
|
-
async function getTurnRecord(conversationId, turnId) {
|
|
2272
|
-
return await materializeStoredTurnRecord(conversationId, turnId, false);
|
|
2273
|
-
}
|
|
2274
|
-
async function getTurnRecordForResume(conversationId, turnId) {
|
|
2275
|
-
return await materializeStoredTurnRecord(conversationId, turnId, true);
|
|
2276
|
-
}
|
|
2277
|
-
function buildStoredRecord(args) {
|
|
2278
|
-
const nowMs = Date.now();
|
|
2279
|
-
return {
|
|
2280
|
-
schemaVersion: TURN_CURSOR_SCHEMA_VERSION,
|
|
2281
|
-
version: (args.previousVersion ?? 0) + 1,
|
|
2282
|
-
conversationId: args.conversationId,
|
|
2283
|
-
turnId: args.turnId,
|
|
2284
|
-
sliceId: args.sliceId,
|
|
2285
|
-
state: args.state,
|
|
2286
|
-
startedAtMs: args.startedAtMs ?? nowMs,
|
|
2287
|
-
lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
|
|
2288
|
-
updatedAtMs: nowMs,
|
|
2289
|
-
committedSeq: args.committedSeq,
|
|
2290
|
-
...definedProps({
|
|
2291
|
-
actor: args.actor,
|
|
2292
|
-
cumulativeToolCallCount: args.cumulativeToolCallCount,
|
|
2293
|
-
dispatchId: args.dispatchId,
|
|
2294
|
-
dispatchOutcome: args.dispatchOutcome,
|
|
2295
|
-
errorMessage: args.errorMessage,
|
|
2296
|
-
historyVersion: args.historyVersion,
|
|
2297
|
-
resumeReason: args.resumeReason,
|
|
2298
|
-
publishExternally: args.publishExternally,
|
|
2299
|
-
source: args.source,
|
|
2300
|
-
resultMessageId: args.resultMessageId,
|
|
2301
|
-
resumedFromSliceId: args.resumedFromSliceId,
|
|
2302
|
-
runtimeContext: args.runtimeContext && args.runtimeContext.length > 0 ? args.runtimeContext : void 0,
|
|
2303
|
-
surface: args.surface,
|
|
2304
|
-
traceId: args.traceId,
|
|
2305
|
-
turnStartSeq: args.turnStartSeq
|
|
2306
|
-
})
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
async function setStoredRecord(args) {
|
|
2310
|
-
const stateAdapter = getStateAdapter();
|
|
2311
|
-
await stateAdapter.connect();
|
|
2312
|
-
const storedRecord = storedTurnRecordSchema.parse(args.record);
|
|
2313
|
-
await args.fence();
|
|
2314
|
-
await recordConversationActivityMetadata({
|
|
2315
|
-
actor: args.actor,
|
|
2316
|
-
conversationStore: args.conversationStore,
|
|
2317
|
-
destination: args.destination,
|
|
2318
|
-
destinationVisibility: args.destinationVisibility,
|
|
2319
|
-
nowMs: Date.now(),
|
|
2320
|
-
source: args.source,
|
|
2321
|
-
summary: {
|
|
2322
|
-
...projectTurnSummary(storedRecord),
|
|
2323
|
-
startedAtMs: storedRecord.startedAtMs,
|
|
2324
|
-
...args.runtimeMetrics
|
|
2325
|
-
}
|
|
2326
|
-
});
|
|
2327
|
-
await args.fence();
|
|
2328
|
-
await stateAdapter.set(
|
|
2329
|
-
turnCursorKey(storedRecord.conversationId, storedRecord.turnId),
|
|
2330
|
-
storedRecord,
|
|
2331
|
-
args.ttlMs
|
|
2332
|
-
);
|
|
2333
|
-
await args.fence();
|
|
2334
|
-
await appendTurnSummary(
|
|
2335
|
-
projectTurnSummary(storedRecord),
|
|
2336
|
-
turnCursorIndexTtlMs(args.indexTtlMs)
|
|
2337
|
-
);
|
|
2338
|
-
return materializeTurnRecord(
|
|
2339
|
-
storedRecord,
|
|
2340
|
-
{
|
|
2341
|
-
messages: [...args.piMessages],
|
|
2342
|
-
provenance: [...args.piMessageProvenance]
|
|
2343
|
-
},
|
|
2344
|
-
args.turnStartMessageIndex,
|
|
2345
|
-
true,
|
|
2346
|
-
args.runtimeMetrics
|
|
2347
|
-
);
|
|
2348
|
-
}
|
|
2349
|
-
async function updateTurnState(args) {
|
|
2350
|
-
const parsed = await getStoredTurnRecord(
|
|
2351
|
-
args.existing.conversationId,
|
|
2352
|
-
args.existing.turnId
|
|
2353
|
-
);
|
|
2354
|
-
if (!parsed || parsed.version !== args.existing.version) {
|
|
2355
|
-
return void 0;
|
|
2356
|
-
}
|
|
2357
|
-
return await setStoredRecord({
|
|
2358
|
-
actor: args.existing.actor,
|
|
2359
|
-
fence: args.fence,
|
|
2360
|
-
piMessages: args.existing.piMessages,
|
|
2361
|
-
piMessageProvenance: args.existing.piMessageProvenance,
|
|
2362
|
-
ttlMs: turnCursorTtlMs(args.state),
|
|
2363
|
-
runtimeMetrics: {
|
|
2364
|
-
cumulativeDurationMs: args.existing.cumulativeDurationMs,
|
|
2365
|
-
...definedProps({
|
|
2366
|
-
channelName: args.existing.channelName,
|
|
2367
|
-
cumulativeUsage: args.existing.cumulativeUsage
|
|
2368
|
-
})
|
|
2369
|
-
},
|
|
2370
|
-
...definedProps({
|
|
2371
|
-
turnStartMessageIndex: args.existing.turnStartMessageIndex
|
|
2372
|
-
}),
|
|
2373
|
-
record: buildStoredRecord({
|
|
2374
|
-
actor: args.existing.actor,
|
|
2375
|
-
conversationId: args.existing.conversationId,
|
|
2376
|
-
turnId: args.existing.turnId,
|
|
2377
|
-
sliceId: args.existing.sliceId,
|
|
2378
|
-
state: args.state,
|
|
2379
|
-
committedSeq: parsed.committedSeq,
|
|
2380
|
-
startedAtMs: parsed.startedAtMs,
|
|
2381
|
-
lastProgressAtMs: parsed.lastProgressAtMs,
|
|
2382
|
-
previousVersion: parsed.version,
|
|
2383
|
-
...definedProps({
|
|
2384
|
-
cumulativeToolCallCount: args.existing.cumulativeToolCallCount,
|
|
2385
|
-
dispatchId: args.existing.dispatchId,
|
|
2386
|
-
dispatchOutcome: args.existing.dispatchOutcome,
|
|
2387
|
-
errorMessage: args.errorMessage ?? args.existing.errorMessage,
|
|
2388
|
-
historyVersion: parsed.historyVersion,
|
|
2389
|
-
resumeReason: args.existing.resumeReason,
|
|
2390
|
-
publishExternally: parsed.publishExternally,
|
|
2391
|
-
source: args.existing.source,
|
|
2392
|
-
resultMessageId: args.resultMessageId ?? args.existing.resultMessageId,
|
|
2393
|
-
resumedFromSliceId: args.existing.resumedFromSliceId,
|
|
2394
|
-
surface: args.existing.surface,
|
|
2395
|
-
traceId: args.existing.traceId,
|
|
2396
|
-
turnStartSeq: parsed.turnStartSeq
|
|
2397
|
-
})
|
|
2398
|
-
})
|
|
2399
|
-
});
|
|
2400
|
-
}
|
|
2401
|
-
async function upsertTurnRecord(args) {
|
|
2402
|
-
return await withTurnCursorMutation(
|
|
2403
|
-
args.conversationId,
|
|
2404
|
-
args.turnId,
|
|
2405
|
-
async (fence) => await upsertTurnRecordLocked(args, fence)
|
|
2406
|
-
);
|
|
2407
|
-
}
|
|
2408
|
-
async function upsertTurnRecordLocked(args, fence) {
|
|
2409
|
-
const existingRecord = await getStoredTurnRecord(
|
|
2410
|
-
args.conversationId,
|
|
2411
|
-
args.turnId
|
|
2412
|
-
);
|
|
2413
|
-
const conversation = await (args.conversationStore ?? getConversationStore()).get({
|
|
2414
|
-
conversationId: args.conversationId
|
|
2415
|
-
});
|
|
2416
|
-
const executionMetrics = executionMetricsForTurn(conversation, args.turnId);
|
|
2417
|
-
const existingDispatchId = existingRecord?.dispatchId ?? (await listTurnSummaries(args.conversationId)).find(
|
|
2418
|
-
(summary) => summary.turnId === args.turnId
|
|
2419
|
-
)?.dispatchId;
|
|
2420
|
-
if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
|
|
2421
|
-
throw new Error(`Turn ${args.turnId} dispatchId cannot be changed`);
|
|
2422
|
-
}
|
|
2423
|
-
const ttlMs = turnCursorTtlMs(args.state, args.ttlMs);
|
|
2424
|
-
const instructionActor = args.actor;
|
|
2425
|
-
const commit = await commitMessages({
|
|
2426
|
-
conversationId: args.conversationId,
|
|
2427
|
-
messages: args.piMessages,
|
|
2428
|
-
...instructionActor ? { newMessageProvenance: instructionProvenanceFor(instructionActor) } : void 0,
|
|
2429
|
-
...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : void 0,
|
|
2430
|
-
...args.turnContexts && args.turnContexts.length > 0 ? {
|
|
2431
|
-
turnContext: {
|
|
2432
|
-
contexts: args.turnContexts,
|
|
2433
|
-
turnId: args.turnId
|
|
2434
|
-
}
|
|
2435
|
-
} : void 0
|
|
2436
|
-
});
|
|
2437
|
-
const durableTurnStartMessageIndex = args.turnStartMessageIndex === void 0 ? void 0 : stripRuntimeTurnContext(
|
|
2438
|
-
args.piMessages.slice(0, args.turnStartMessageIndex)
|
|
2439
|
-
).length;
|
|
2440
|
-
const runtimeContext = retainRuntimeTurnContext(args.piMessages);
|
|
2441
|
-
const retainedRuntimeContext = runtimeContext.length > 0 ? runtimeContext : existingRecord?.historyVersion === commit.historyVersion ? existingRecord.runtimeContext : void 0;
|
|
2442
|
-
const turnStartSeq = durableTurnStartMessageIndex === void 0 ? existingRecord?.turnStartSeq : durableTurnStartMessageIndex <= 0 ? -1 : commit.messageSeqs[durableTurnStartMessageIndex - 1] ?? commit.committedSeq;
|
|
2443
|
-
const turnStartMessageIndex = durableTurnStartMessageIndex ?? (turnStartSeq === void 0 ? void 0 : commit.messageSeqs.filter((seq) => seq <= turnStartSeq).length);
|
|
2444
|
-
const turnSource = args.source ?? existingRecord?.source;
|
|
2445
|
-
const dispatchId = args.dispatchId ?? existingRecord?.dispatchId;
|
|
2446
|
-
const hasProviderLocation = Boolean(conversation?.location) || args.destination?.platform === "slack";
|
|
2447
|
-
const publishExternally = dispatchId || turnSource ? hasProviderLocation && (Boolean(dispatchId) || turnSource?.kind === "slack" || turnSource?.kind === "resource_event") : existingRecord?.publishExternally ?? false;
|
|
2448
|
-
return await setStoredRecord({
|
|
2449
|
-
actor: existingRecord?.actor ?? args.actor,
|
|
2450
|
-
fence,
|
|
2451
|
-
conversationStore: args.conversationStore,
|
|
2452
|
-
destination: args.destination,
|
|
2453
|
-
destinationVisibility: args.destinationVisibility,
|
|
2454
|
-
source: args.source,
|
|
2455
|
-
piMessages: commit.messages,
|
|
2456
|
-
piMessageProvenance: commit.provenance,
|
|
2457
|
-
ttlMs,
|
|
2458
|
-
// Checkpoint commit also updates conversation execution metrics in SQL.
|
|
2459
|
-
// Channel/duration/usage are not Redis resume-cursor fields.
|
|
2460
|
-
runtimeMetrics: {
|
|
2461
|
-
channelName: args.channelName ?? conversation?.channelName,
|
|
2462
|
-
cumulativeDurationMs: args.cumulativeDurationMs ?? executionMetrics?.durationMs ?? 0,
|
|
2463
|
-
cumulativeUsage: args.cumulativeUsage ?? executionMetrics?.usage
|
|
2464
|
-
},
|
|
2465
|
-
// Pass through only an explicit override so indexes stay on the resume window
|
|
2466
|
-
// by default even when the record takes a short terminal TTL.
|
|
2467
|
-
...definedProps({ indexTtlMs: args.ttlMs }),
|
|
2468
|
-
turnStartMessageIndex,
|
|
2469
|
-
record: buildStoredRecord({
|
|
2470
|
-
actor: existingRecord?.actor ?? args.actor,
|
|
2471
|
-
conversationId: args.conversationId,
|
|
2472
|
-
turnId: args.turnId,
|
|
2473
|
-
sliceId: args.sliceId,
|
|
2474
|
-
state: args.state,
|
|
2475
|
-
committedSeq: commit.committedSeq,
|
|
2476
|
-
historyVersion: commit.historyVersion,
|
|
2477
|
-
previousVersion: existingRecord?.version,
|
|
2478
|
-
...definedProps({
|
|
2479
|
-
cumulativeToolCallCount: args.cumulativeToolCallCount ?? existingRecord?.cumulativeToolCallCount,
|
|
2480
|
-
dispatchId,
|
|
2481
|
-
dispatchOutcome: args.dispatchOutcome ?? existingRecord?.dispatchOutcome,
|
|
2482
|
-
errorMessage: args.errorMessage,
|
|
2483
|
-
lastProgressAtMs: args.lastProgressAtMs,
|
|
2484
|
-
resumeReason: args.resumeReason,
|
|
2485
|
-
publishExternally,
|
|
2486
|
-
source: args.source ?? existingRecord?.source,
|
|
2487
|
-
resultMessageId: args.resultMessageId ?? existingRecord?.resultMessageId,
|
|
2488
|
-
resumedFromSliceId: args.resumedFromSliceId,
|
|
2489
|
-
runtimeContext: args.state === "running" || args.state === "paused" ? retainedRuntimeContext : void 0,
|
|
2490
|
-
startedAtMs: existingRecord?.startedAtMs,
|
|
2491
|
-
surface: args.surface ?? existingRecord?.surface,
|
|
2492
|
-
traceId: args.traceId ?? existingRecord?.traceId,
|
|
2493
|
-
turnStartSeq
|
|
2494
|
-
})
|
|
2495
|
-
})
|
|
2496
|
-
});
|
|
2497
|
-
}
|
|
2498
|
-
async function recordTurnSummary(args) {
|
|
2499
|
-
await withTurnCursorMutation(
|
|
2500
|
-
args.conversationId,
|
|
2501
|
-
args.turnId,
|
|
2502
|
-
async (fence) => await recordTurnSummaryOwned(args, fence)
|
|
2503
|
-
);
|
|
2504
|
-
}
|
|
2505
|
-
async function recordTurnSummaryOwned(args, fence) {
|
|
2506
|
-
const stored = await getStoredTurnRecord(args.conversationId, args.turnId);
|
|
2507
|
-
const conversationStore = args.conversationStore ?? getConversationStore();
|
|
2508
|
-
const conversation = await conversationStore.get({
|
|
2509
|
-
conversationId: args.conversationId
|
|
2510
|
-
});
|
|
2511
|
-
const executionMetrics = executionMetricsForTurn(conversation, args.turnId);
|
|
2512
|
-
const priorSummary = (await listTurnSummaries(args.conversationId)).find(
|
|
2513
|
-
(summary2) => summary2.turnId === args.turnId
|
|
2514
|
-
);
|
|
2515
|
-
const terminalSummary = priorSummary && (priorSummary.state === "completed" || priorSummary.state === "failed" || priorSummary.state === "abandoned") ? priorSummary : void 0;
|
|
2516
|
-
const terminalRecord = stored && (stored.state === "completed" || stored.state === "failed" || stored.state === "abandoned") ? stored : void 0;
|
|
2517
|
-
const existing = terminalSummary ?? terminalRecord ?? stored ?? priorSummary;
|
|
2518
|
-
if ((terminalSummary || terminalRecord) && (args.state === "running" || args.state === "paused")) {
|
|
2519
|
-
return;
|
|
2520
|
-
}
|
|
2521
|
-
const existingDispatchId = existing?.dispatchId;
|
|
2522
|
-
const existingDispatchOutcome = priorSummary?.dispatchOutcome ?? stored?.dispatchOutcome;
|
|
2523
|
-
const existingResultMessageId = priorSummary?.resultMessageId ?? stored?.resultMessageId;
|
|
2524
|
-
if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
|
|
2525
|
-
throw new Error(`Turn ${args.turnId} dispatchId cannot be changed`);
|
|
2526
|
-
}
|
|
2527
|
-
const nowMs = Date.now();
|
|
2528
|
-
const ttlMs = turnCursorIndexTtlMs(args.ttlMs);
|
|
2529
|
-
const summary = {
|
|
2530
|
-
schemaVersion: TURN_CURSOR_SCHEMA_VERSION,
|
|
2531
|
-
version: existing?.version ?? 0,
|
|
2532
|
-
conversationId: args.conversationId,
|
|
2533
|
-
turnId: args.turnId,
|
|
2534
|
-
sliceId: args.sliceId,
|
|
2535
|
-
state: args.state,
|
|
2536
|
-
updatedAtMs: nowMs,
|
|
2537
|
-
...definedProps({
|
|
2538
|
-
dispatchId: args.dispatchId ?? existingDispatchId,
|
|
2539
|
-
dispatchOutcome: args.dispatchOutcome ?? existingDispatchOutcome,
|
|
2540
|
-
resumeReason: args.resumeReason,
|
|
2541
|
-
resultMessageId: args.resultMessageId ?? existingResultMessageId,
|
|
2542
|
-
surface: args.surface ?? existing?.surface
|
|
2543
|
-
})
|
|
2544
|
-
};
|
|
2545
|
-
await fence();
|
|
2546
|
-
await recordConversationActivityMetadata({
|
|
2547
|
-
actor: args.actor,
|
|
2548
|
-
conversationStore,
|
|
2549
|
-
destination: args.destination,
|
|
2550
|
-
destinationVisibility: args.destinationVisibility,
|
|
2551
|
-
nowMs,
|
|
2552
|
-
source: args.source,
|
|
2553
|
-
summary: {
|
|
2554
|
-
...summary,
|
|
2555
|
-
channelName: args.channelName ?? conversation?.channelName,
|
|
2556
|
-
cumulativeDurationMs: args.cumulativeDurationMs ?? executionMetrics?.durationMs ?? 0,
|
|
2557
|
-
cumulativeUsage: args.cumulativeUsage ?? executionMetrics?.usage,
|
|
2558
|
-
startedAtMs: stored?.startedAtMs ?? args.startedAtMs ?? nowMs
|
|
2559
|
-
}
|
|
2560
|
-
});
|
|
2561
|
-
await fence();
|
|
2562
|
-
await appendTurnSummary(summary, ttlMs);
|
|
2563
|
-
}
|
|
2564
|
-
async function readTurnSummaries(key) {
|
|
2565
|
-
const stateAdapter = getStateAdapter();
|
|
2566
|
-
await stateAdapter.connect();
|
|
2567
|
-
const values = await stateAdapter.getList(key);
|
|
2568
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
2569
|
-
for (const value of [...values].reverse()) {
|
|
2570
|
-
let summary;
|
|
2571
|
-
try {
|
|
2572
|
-
summary = parseTurnSummary(value);
|
|
2573
|
-
} catch {
|
|
2574
|
-
continue;
|
|
2575
|
-
}
|
|
2576
|
-
const summaryKey = `${summary.conversationId}:${summary.turnId}`;
|
|
2577
|
-
if (!summaries.has(summaryKey)) {
|
|
2578
|
-
summaries.set(summaryKey, summary);
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
return [...summaries.values()].sort(
|
|
2582
|
-
(left, right) => right.updatedAtMs - left.updatedAtMs
|
|
2583
|
-
);
|
|
2584
|
-
}
|
|
2585
|
-
async function listTurnSummaries(conversationId) {
|
|
2586
|
-
return readTurnSummaries(turnCursorIndexKey(conversationId));
|
|
2587
|
-
}
|
|
2588
|
-
async function abandonTurnRecord(args) {
|
|
2589
|
-
return await withTurnCursorMutation(
|
|
2590
|
-
args.conversationId,
|
|
2591
|
-
args.turnId,
|
|
2592
|
-
async (fence) => {
|
|
2593
|
-
const existing = await getTurnRecord(args.conversationId, args.turnId);
|
|
2594
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned") {
|
|
2595
|
-
return void 0;
|
|
2596
|
-
}
|
|
2597
|
-
return await updateTurnState({
|
|
2598
|
-
existing,
|
|
2599
|
-
fence,
|
|
2600
|
-
state: "abandoned",
|
|
2601
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
2602
|
-
});
|
|
2603
|
-
}
|
|
2604
|
-
);
|
|
2605
|
-
}
|
|
2606
|
-
async function completeTurnRecord(args) {
|
|
2607
|
-
return await withTurnCursorMutation(
|
|
2608
|
-
args.conversationId,
|
|
2609
|
-
args.turnId,
|
|
2610
|
-
async (fence) => {
|
|
2611
|
-
const existing = await getTurnRecord(args.conversationId, args.turnId);
|
|
2612
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
|
|
2613
|
-
return void 0;
|
|
2614
|
-
}
|
|
2615
|
-
return await updateTurnState({
|
|
2616
|
-
existing,
|
|
2617
|
-
fence,
|
|
2618
|
-
resultMessageId: args.resultMessageId,
|
|
2619
|
-
state: "completed"
|
|
2620
|
-
});
|
|
2621
|
-
}
|
|
2622
|
-
);
|
|
2623
|
-
}
|
|
2624
|
-
async function failTurnRecord(args) {
|
|
2625
|
-
return await withTurnCursorMutation(
|
|
2626
|
-
args.conversationId,
|
|
2627
|
-
args.turnId,
|
|
2628
|
-
async (fence) => {
|
|
2629
|
-
const existing = await getTurnRecord(args.conversationId, args.turnId);
|
|
2630
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
|
|
2631
|
-
return void 0;
|
|
2632
|
-
}
|
|
2633
|
-
return await updateTurnState({
|
|
2634
|
-
existing,
|
|
2635
|
-
fence,
|
|
2636
|
-
state: "failed",
|
|
2637
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
2638
|
-
});
|
|
2639
|
-
}
|
|
2640
|
-
);
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
// src/chat/services/turn-limit.ts
|
|
2644
|
-
var TurnSliceLimitExceededError = class extends Error {
|
|
2645
|
-
constructor(maxSlices) {
|
|
2646
|
-
super(`Agent turn exceeded execution limit (${maxSlices} slices)`);
|
|
2647
|
-
this.name = "TurnSliceLimitExceededError";
|
|
2648
|
-
}
|
|
2649
|
-
};
|
|
2650
|
-
var TurnToolCallLimitExceededError = class extends Error {
|
|
2651
|
-
constructor(maxToolCalls) {
|
|
2652
|
-
super(`Agent turn exceeded execution limit (${maxToolCalls} tool calls)`);
|
|
2653
|
-
this.name = "TurnToolCallLimitExceededError";
|
|
2654
|
-
}
|
|
2655
|
-
};
|
|
2656
|
-
function isDirectTurnExecutionLimitError(error) {
|
|
2657
|
-
return error instanceof TurnSliceLimitExceededError || error instanceof TurnToolCallLimitExceededError;
|
|
2658
|
-
}
|
|
2659
|
-
function isTurnExecutionLimitExceededError(error) {
|
|
2660
|
-
if (isDirectTurnExecutionLimitError(error)) {
|
|
2661
|
-
return true;
|
|
2662
|
-
}
|
|
2663
|
-
return error instanceof Error && isDirectTurnExecutionLimitError(error.cause);
|
|
2664
|
-
}
|
|
2665
|
-
function assertTurnToolCallLimit(toolCallCount, maxToolCalls) {
|
|
2666
|
-
if (toolCallCount <= maxToolCalls) {
|
|
2667
|
-
return;
|
|
2668
|
-
}
|
|
2669
|
-
throw new TurnToolCallLimitExceededError(maxToolCalls);
|
|
2670
|
-
}
|
|
2671
|
-
function buildTurnLimitResponse(eventId) {
|
|
2672
|
-
return `I couldn't finish this request because this turn reached its execution limit. Please try again with a smaller or more specific request. Reference: \`event_id=${eventId}\`.`;
|
|
2673
|
-
}
|
|
2674
|
-
function buildTurnErrorResponse(error, eventId) {
|
|
2675
|
-
return isTurnExecutionLimitExceededError(error) ? buildTurnLimitResponse(eventId) : buildTurnFailureResponse(eventId);
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
// src/chat/task-execution/checkpoint.ts
|
|
2679
|
-
function definedProps2(values) {
|
|
2680
|
-
return Object.fromEntries(
|
|
2681
|
-
Object.entries(values).filter(([, value]) => value !== void 0)
|
|
2682
|
-
);
|
|
2683
|
-
}
|
|
2684
|
-
function addDurationMs(prior, current) {
|
|
2685
|
-
const total = [prior, current].reduce((sum, value) => {
|
|
2686
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2687
|
-
return sum;
|
|
2688
|
-
}
|
|
2689
|
-
return (sum ?? 0) + Math.max(0, Math.floor(value));
|
|
2690
|
-
}, void 0);
|
|
2691
|
-
return total;
|
|
2692
|
-
}
|
|
2693
|
-
function continuableMessages(messages, fallback) {
|
|
2694
|
-
const current = trimTrailingAssistantMessages(messages);
|
|
2695
|
-
if (current.length > 0 && isContinuablePiBoundary(current)) {
|
|
2696
|
-
return current;
|
|
2697
|
-
}
|
|
2698
|
-
return trimTrailingAssistantMessages(fallback ?? []);
|
|
2699
|
-
}
|
|
2700
|
-
async function loadTurnCheckpoint(args) {
|
|
2701
|
-
const record = await getTurnRecordForResume(args.conversationId, args.turnId);
|
|
2702
|
-
if (record?.state === "paused") {
|
|
2703
|
-
return { resumed: true, sliceId: record.sliceId, record };
|
|
2704
|
-
}
|
|
2705
|
-
return { resumed: false, sliceId: 1, record };
|
|
2706
|
-
}
|
|
2707
|
-
async function saveTurnCheckpoint(args) {
|
|
2708
|
-
if (args.mode === "running") {
|
|
2709
|
-
return await saveRunning(args);
|
|
2710
|
-
}
|
|
2711
|
-
if (args.mode === "paused") {
|
|
2712
|
-
return await savePaused(args);
|
|
2713
|
-
}
|
|
2714
|
-
await saveDone(args);
|
|
2715
|
-
return void 0;
|
|
2716
|
-
}
|
|
2717
|
-
function sharedWrite(args, latest) {
|
|
2718
|
-
return {
|
|
2719
|
-
conversationId: args.conversationId,
|
|
2720
|
-
turnId: args.turnId,
|
|
2721
|
-
...definedProps2({
|
|
2722
|
-
actor: args.actor,
|
|
2723
|
-
channelName: args.channelName ?? latest?.channelName,
|
|
2724
|
-
destination: args.destination,
|
|
2725
|
-
destinationVisibility: args.destinationVisibility,
|
|
2726
|
-
dispatchId: args.dispatchId ?? latest?.dispatchId,
|
|
2727
|
-
source: args.source,
|
|
2728
|
-
surface: args.surface ?? latest?.surface,
|
|
2729
|
-
traceId: getActiveTraceId() ?? latest?.traceId,
|
|
2730
|
-
turnStartMessageIndex: args.turnStartMessageIndex ?? latest?.turnStartMessageIndex,
|
|
2731
|
-
cumulativeToolCallCount: args.cumulativeToolCallCount ?? latest?.cumulativeToolCallCount
|
|
2732
|
-
})
|
|
2733
|
-
};
|
|
2734
|
-
}
|
|
2735
|
-
async function saveRunning(args) {
|
|
2736
|
-
if (args.messages.length === 0 || !isContinuablePiBoundary(args.messages)) {
|
|
2737
|
-
return void 0;
|
|
2738
|
-
}
|
|
2739
|
-
try {
|
|
2740
|
-
const latest = await getTurnRecord(args.conversationId, args.turnId);
|
|
2741
|
-
return await upsertTurnRecord({
|
|
2742
|
-
...sharedWrite(args, latest),
|
|
2743
|
-
...definedProps2({
|
|
2744
|
-
trailingMessageProvenance: args.trailingMessageProvenance,
|
|
2745
|
-
turnContexts: args.turnContexts
|
|
2746
|
-
}),
|
|
2747
|
-
cumulativeDurationMs: latest?.cumulativeDurationMs,
|
|
2748
|
-
cumulativeUsage: latest?.cumulativeUsage,
|
|
2749
|
-
piMessages: args.messages,
|
|
2750
|
-
sliceId: args.sliceId,
|
|
2751
|
-
state: "running"
|
|
2752
|
-
});
|
|
2753
|
-
} catch (error) {
|
|
2754
|
-
if (!(error instanceof AgentHistoryBranchError)) {
|
|
2755
|
-
logException(error, "agent.turn.checkpoint.running.failed", {
|
|
2756
|
-
"app.ai.resume_conversation_id": args.conversationId,
|
|
2757
|
-
"app.ai.resume_session_id": args.turnId,
|
|
2758
|
-
"app.ai.resume_slice_id": args.sliceId
|
|
2759
|
-
});
|
|
2760
|
-
}
|
|
2761
|
-
return void 0;
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
async function savePaused(args) {
|
|
2765
|
-
const keepSlice = args.reason === "yield";
|
|
2766
|
-
const nextSliceId = keepSlice ? args.sliceId : args.sliceId + 1;
|
|
2767
|
-
try {
|
|
2768
|
-
const latest = await getTurnRecord(args.conversationId, args.turnId);
|
|
2769
|
-
const messages = args.reason === "yield" ? [...args.messages] : continuableMessages(args.messages, latest?.piMessages);
|
|
2770
|
-
if (args.reason === "auth") {
|
|
2771
|
-
if (messages.length > 0 && !isContinuablePiBoundary(messages)) {
|
|
2772
|
-
return void 0;
|
|
2773
|
-
}
|
|
2774
|
-
} else if (messages.length === 0 || !isContinuablePiBoundary(messages)) {
|
|
2775
|
-
return void 0;
|
|
2776
|
-
}
|
|
2777
|
-
const base = {
|
|
2778
|
-
...sharedWrite(args, latest),
|
|
2779
|
-
...definedProps2({
|
|
2780
|
-
cumulativeDurationMs: addDurationMs(
|
|
2781
|
-
latest?.cumulativeDurationMs,
|
|
2782
|
-
args.durationMs
|
|
2783
|
-
),
|
|
2784
|
-
cumulativeUsage: addAgentTurnUsage(latest?.cumulativeUsage, args.usage),
|
|
2785
|
-
errorMessage: args.errorMessage
|
|
2786
|
-
}),
|
|
2787
|
-
piMessages: messages,
|
|
2788
|
-
resumeReason: args.reason
|
|
2789
|
-
};
|
|
2790
|
-
if (!keepSlice && nextSliceId > botConfig.maxSlicesPerTurn) {
|
|
2791
|
-
const error = new TurnSliceLimitExceededError(botConfig.maxSlicesPerTurn);
|
|
2792
|
-
await upsertTurnRecord({
|
|
2793
|
-
...base,
|
|
2794
|
-
...definedProps2({ resumedFromSliceId: latest?.resumedFromSliceId }),
|
|
2795
|
-
errorMessage: error.message,
|
|
2796
|
-
sliceId: args.sliceId,
|
|
2797
|
-
state: "failed"
|
|
2798
|
-
});
|
|
2799
|
-
throw error;
|
|
2800
|
-
}
|
|
2801
|
-
return await upsertTurnRecord({
|
|
2802
|
-
...base,
|
|
2803
|
-
...definedProps2({
|
|
2804
|
-
resumedFromSliceId: keepSlice ? latest?.resumedFromSliceId : args.sliceId
|
|
2805
|
-
}),
|
|
2806
|
-
sliceId: nextSliceId,
|
|
2807
|
-
// Stored name is historical; means "paused, may continue".
|
|
2808
|
-
state: "paused"
|
|
2809
|
-
});
|
|
2810
|
-
} catch (error) {
|
|
2811
|
-
if (error instanceof TurnSliceLimitExceededError) {
|
|
2812
|
-
throw error;
|
|
2813
|
-
}
|
|
2814
|
-
logException(error, "agent.turn.checkpoint.paused.failed", {
|
|
2815
|
-
"app.ai.resume_conversation_id": args.conversationId,
|
|
2816
|
-
"app.ai.resume_session_id": args.turnId,
|
|
2817
|
-
"app.ai.resume_from_slice_id": args.sliceId,
|
|
2818
|
-
"app.ai.resume_next_slice_id": nextSliceId,
|
|
2819
|
-
"app.ai.resume_reason": args.reason
|
|
2820
|
-
});
|
|
2821
|
-
return void 0;
|
|
2822
|
-
}
|
|
2823
|
-
}
|
|
2824
|
-
async function saveDone(args) {
|
|
2825
|
-
let latest;
|
|
2826
|
-
await persistWithRetry(async () => {
|
|
2827
|
-
latest = await getTurnRecord(args.conversationId, args.turnId);
|
|
2828
|
-
});
|
|
2829
|
-
const sliceId = args.sliceId ?? latest?.sliceId;
|
|
2830
|
-
if (sliceId === void 0) {
|
|
2831
|
-
throw new Error(
|
|
2832
|
-
"Completed turn checkpoint requires a slice id from the caller or the latest stored record"
|
|
2833
|
-
);
|
|
2834
|
-
}
|
|
2835
|
-
await persistWithRetry(async () => {
|
|
2836
|
-
await upsertTurnRecord({
|
|
2837
|
-
...sharedWrite(args, latest),
|
|
2838
|
-
...definedProps2({
|
|
2839
|
-
cumulativeDurationMs: addDurationMs(
|
|
2840
|
-
latest?.cumulativeDurationMs,
|
|
2841
|
-
args.durationMs
|
|
2842
|
-
),
|
|
2843
|
-
cumulativeUsage: addAgentTurnUsage(latest?.cumulativeUsage, args.usage),
|
|
2844
|
-
dispatchOutcome: args.dispatchOutcome ?? latest?.dispatchOutcome,
|
|
2845
|
-
errorMessage: args.errorMessage,
|
|
2846
|
-
resultMessageId: args.resultMessageId ?? latest?.resultMessageId
|
|
2847
|
-
}),
|
|
2848
|
-
piMessages: args.messages,
|
|
2849
|
-
sliceId,
|
|
2850
|
-
state: args.mode
|
|
2851
|
-
});
|
|
2852
|
-
});
|
|
2853
|
-
}
|
|
2854
|
-
|
|
2855
|
-
export {
|
|
2856
|
-
TURN_CONTEXT_TAG,
|
|
2857
|
-
isAssistantMessage,
|
|
2858
|
-
isToolResultMessage,
|
|
2859
|
-
isToolResultError,
|
|
2860
|
-
normalizeToolNameFromResult,
|
|
2861
|
-
getPiMessageRole,
|
|
2862
|
-
isContinuablePiBoundary,
|
|
2863
|
-
extractAssistantText,
|
|
2864
|
-
getTerminalAssistantMessages,
|
|
2865
|
-
trimTrailingAssistantMessages,
|
|
2866
|
-
hasRuntimeTurnContext,
|
|
2867
|
-
retainRuntimeTurnContext,
|
|
2868
|
-
instructionTextForProjection,
|
|
2869
|
-
getUserMessageInstructionText,
|
|
2870
|
-
stripRuntimeTurnContext,
|
|
2871
|
-
historyItemFromPiMessage,
|
|
2872
|
-
piMessageFromHistoryItem,
|
|
2873
|
-
estimateTextTokens,
|
|
2874
|
-
getAgentContextCompactionTriggerTokens,
|
|
2875
|
-
getAgentContextInputLimitTokens,
|
|
2876
|
-
normalizeConversationText,
|
|
2877
|
-
estimateConversationContextTokens,
|
|
2878
|
-
upsertConversationMessage,
|
|
2879
|
-
recordDeliveredAssistantMessage,
|
|
2880
|
-
turnHasReply,
|
|
2881
|
-
markConversationMessage,
|
|
2882
|
-
buildConversationContext,
|
|
2883
|
-
appendThreadContextMessages,
|
|
2884
|
-
createConversationMemoryService,
|
|
2885
|
-
isHumanConversationMessage,
|
|
2886
|
-
getConversationMessageSlackTs,
|
|
2887
|
-
resolveConversationTitle,
|
|
2888
|
-
persistConversationMessageSummaries,
|
|
2889
|
-
projectConversationMessages,
|
|
2890
|
-
hydrateConversationMessages,
|
|
2891
|
-
persistConversationMessages,
|
|
2892
|
-
appendConversationMessages,
|
|
2893
|
-
loadProjection,
|
|
2894
|
-
loadConversationProjection,
|
|
2895
|
-
openConversationProjection,
|
|
2896
|
-
loadConnectedMcpProviders,
|
|
2897
|
-
commitMessages,
|
|
2898
|
-
commitAcceptedReply,
|
|
2899
|
-
recordMcpProviderConnected,
|
|
2900
|
-
recordAuthorizationRequested,
|
|
2901
|
-
recordAuthorizationCompleted,
|
|
2902
|
-
recordAuthenticationLinked,
|
|
2903
|
-
recordAgentsInstructionsUpdated,
|
|
2904
|
-
recordSubscribedReplyRoute,
|
|
2905
|
-
loadTurnRoute,
|
|
2906
|
-
recordTurnRoute,
|
|
2907
|
-
recordToolExecutionStarted,
|
|
2908
|
-
recordAttachmentsDelivered,
|
|
2909
|
-
recordGuardianActionReviewed,
|
|
2910
|
-
MAX_TEXT_CHARS,
|
|
2911
|
-
RIPGREP_EXCLUDED_GLOBS,
|
|
2912
|
-
DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
|
|
2913
|
-
positiveInteger2 as positiveInteger,
|
|
2914
|
-
normalizeToLf,
|
|
2915
|
-
truncateText,
|
|
2916
|
-
isMissingPathError,
|
|
2917
|
-
missingPathSearchResult,
|
|
2918
|
-
resolveWorkspacePath,
|
|
2919
|
-
getRipgrepSearchLocation,
|
|
2920
|
-
collectFiles,
|
|
2921
|
-
AGENTS_REPLACEMENT_NOTICE,
|
|
2922
|
-
AGENTS_REMOVAL_NOTICE,
|
|
2923
|
-
listRepositoryDirectories,
|
|
2924
|
-
resolveRepositoryInstructionsForDirectories,
|
|
2925
|
-
renderAgentsInstructions,
|
|
2926
|
-
buildAgentsInstructionsMessage,
|
|
2927
|
-
findVisibleAgentsInstructions,
|
|
2928
|
-
TurnSliceLimitExceededError,
|
|
2929
|
-
isTurnExecutionLimitExceededError,
|
|
2930
|
-
assertTurnToolCallLimit,
|
|
2931
|
-
buildTurnErrorResponse,
|
|
2932
|
-
persistWithRetry,
|
|
2933
|
-
getTurnRecord,
|
|
2934
|
-
getTurnRecordForResume,
|
|
2935
|
-
recordTurnSummary,
|
|
2936
|
-
listTurnSummaries,
|
|
2937
|
-
abandonTurnRecord,
|
|
2938
|
-
completeTurnRecord,
|
|
2939
|
-
failTurnRecord,
|
|
2940
|
-
continuableMessages,
|
|
2941
|
-
loadTurnCheckpoint,
|
|
2942
|
-
saveTurnCheckpoint
|
|
2943
|
-
};
|