@sentry/junior 0.121.0 → 0.122.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-CQDGDBCP.js → agent-hooks-65WXVKKX.js} +8 -8
- package/dist/api.js +14 -14
- package/dist/app.js +604 -997
- package/dist/{catalog-runtime-AD3YNTTM.js → catalog-runtime-VNNMSKCB.js} +5 -5
- package/dist/chat/agent/index.d.ts +19 -2
- package/dist/chat/agent/request.d.ts +8 -8
- package/dist/chat/agent/resume.d.ts +1 -3
- package/dist/chat/agent/retry-usage.d.ts +8 -0
- package/dist/chat/agent/skills.d.ts +0 -9
- package/dist/chat/conversations/messages.d.ts +7 -0
- package/dist/chat/conversations/projection.d.ts +16 -0
- package/dist/chat/logging.d.ts +46 -13
- package/dist/chat/mcp/tool-manager.d.ts +11 -0
- package/dist/chat/plugins/agent-hooks.d.ts +8 -0
- package/dist/chat/runtime/processing-reaction.d.ts +2 -4
- package/dist/chat/runtime/slack-runtime.d.ts +2 -2
- package/dist/chat/services/assistant-reply.d.ts +16 -0
- package/dist/chat/services/empty-output-continuation.d.ts +20 -0
- package/dist/chat/services/turn-failure-response.d.ts +1 -3
- package/dist/chat/services/turn-result.d.ts +0 -5
- package/dist/chat/services/turn-session-record.d.ts +0 -11
- package/dist/chat/tool-support/zod-tool.d.ts +11 -6
- package/dist/chat/tools/execute-tool.d.ts +1 -33
- package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -2
- package/dist/chat/tools/query-conversation-events.d.ts +3 -1
- package/dist/chat/tools/sandbox/bash.d.ts +1 -33
- package/dist/chat/tools/sandbox/edit-file.d.ts +1 -33
- package/dist/chat/tools/sandbox/find-files.d.ts +1 -33
- package/dist/chat/tools/sandbox/grep.d.ts +1 -33
- package/dist/chat/tools/sandbox/list-dir.d.ts +1 -33
- package/dist/chat/tools/sandbox/read-file.d.ts +1 -33
- package/dist/chat/tools/sandbox/write-file.d.ts +1 -33
- package/dist/{chunk-VWBKDPMQ.js → chunk-3KYLHUVC.js} +487 -686
- package/dist/{chunk-JZ47AFSH.js → chunk-4JPJTFUH.js} +37 -45
- package/dist/{chunk-ET2SDCNV.js → chunk-4ZV2GNHX.js} +16 -31
- package/dist/{chunk-WUP7NWMP.js → chunk-73TQBD5S.js} +1 -1
- package/dist/{chunk-Z2XXPUZG.js → chunk-BXT63TNS.js} +2061 -2015
- package/dist/{chunk-JYDIBXFY.js → chunk-GPQLJDT5.js} +13 -19
- package/dist/{chunk-LE77HHF3.js → chunk-HGREP4UX.js} +1 -1
- package/dist/{chunk-XDRGYDED.js → chunk-IEE5CSAP.js} +1 -1
- package/dist/{chunk-D2EMOH3N.js → chunk-LZ4XXS76.js} +3 -3
- package/dist/{chunk-254MFDOF.js → chunk-M7KAZXTE.js} +2 -2
- package/dist/{chunk-ITMEXX5H.js → chunk-MSDKKTRG.js} +4 -4
- package/dist/{chunk-UXQ5XSA2.js → chunk-PXPBBOIU.js} +2 -2
- package/dist/{chunk-IK4QSS5H.js → chunk-PYS4QZAF.js} +1 -1
- package/dist/{chunk-7HYV7KFM.js → chunk-QE33ATQW.js} +83 -92
- package/dist/{chunk-HDFGNU5E.js → chunk-S7G34U75.js} +3 -1
- package/dist/{chunk-IDXLBKPS.js → chunk-TI7SYDD4.js} +11 -22
- package/dist/{chunk-3DBMANUI.js → chunk-U5PRRZMZ.js} +22 -45
- package/dist/{chunk-KLUMVJRQ.js → chunk-VLRECFIC.js} +133 -206
- package/dist/{chunk-UURFXX66.js → chunk-WFJ2EG4E.js} +1 -1
- package/dist/{chunk-TRECK7RV.js → chunk-XBYNPBPQ.js} +14 -29
- package/dist/cli/chat.js +22 -22
- package/dist/cli/check.js +6 -6
- package/dist/cli/plugins.js +11 -11
- package/dist/cli/snapshot-create.js +9 -9
- package/dist/cli/upgrade.js +6 -6
- package/dist/{db-7HQT2SXQ.js → db-36HXKAKH.js} +6 -6
- package/dist/instrumentation.js +3 -3
- package/dist/nitro.js +2 -2
- package/dist/{oauth-callback-server-VHTUCNHK.js → oauth-callback-server-UKOO7KKP.js} +15 -15
- package/dist/{runner-3ONPZRED.js → runner-M66KMCQ7.js} +62 -51
- package/dist/{skills-WGAYGGPA.js → skills-TTXTVFOG.js} +6 -6
- package/dist/{validation-ZIWNBG4D.js → validation-TGASRGNN.js} +6 -6
- package/dist/version.js +1 -1
- package/package.json +8 -8
|
@@ -13,17 +13,17 @@ import {
|
|
|
13
13
|
instructionProvenanceFor,
|
|
14
14
|
sanitizePostgresJson,
|
|
15
15
|
withConversationEventLock
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-M7KAZXTE.js";
|
|
17
17
|
import {
|
|
18
18
|
getStateAdapter,
|
|
19
19
|
sleep
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-WFJ2EG4E.js";
|
|
21
21
|
import {
|
|
22
22
|
botConfig,
|
|
23
23
|
escapeXml,
|
|
24
24
|
resolveGatewayModel,
|
|
25
25
|
unwrapCurrentInstruction
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-TI7SYDD4.js";
|
|
27
27
|
import {
|
|
28
28
|
toStoredSlackActor
|
|
29
29
|
} from "./chunk-HZF5P5UN.js";
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
logException,
|
|
33
33
|
logWarn,
|
|
34
34
|
setSpanAttributes
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-4JPJTFUH.js";
|
|
36
36
|
import {
|
|
37
37
|
isRecord,
|
|
38
38
|
toOptionalNumber,
|
|
@@ -147,2192 +147,2231 @@ function stripRuntimeTurnContext(messages) {
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// src/chat/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}).passthrough().transform((value) => value);
|
|
158
|
-
var piContentMessageSchema = z.object({
|
|
159
|
-
content: z.array(z.unknown()),
|
|
160
|
-
role: z.string().min(1)
|
|
161
|
-
}).passthrough().transform((value) => value);
|
|
162
|
-
|
|
163
|
-
// src/chat/pi/conversation-events.ts
|
|
164
|
-
function authorizationObservationMessage(data, createdAtMs) {
|
|
165
|
-
const label = data.kind === "mcp" ? "MCP authorization" : "Authorization";
|
|
166
|
-
return piMessageSchema.parse({
|
|
167
|
-
role: "user",
|
|
168
|
-
content: [
|
|
169
|
-
{
|
|
170
|
-
type: "text",
|
|
171
|
-
text: `${label} completed for provider "${data.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
172
|
-
}
|
|
173
|
-
],
|
|
174
|
-
timestamp: createdAtMs
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
function durableMessages(message) {
|
|
178
|
-
return stripRuntimeTurnContext([message]);
|
|
150
|
+
// src/chat/services/context-budget.ts
|
|
151
|
+
var COMPACTION_TRIGGER_RATIO = 0.9;
|
|
152
|
+
var CONTEXT_INPUT_LIMIT_RATIO = 0.95;
|
|
153
|
+
var COMPACTION_TARGET_RATIO = 0.8;
|
|
154
|
+
var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
|
|
155
|
+
function positiveInteger(value, fallback) {
|
|
156
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
179
157
|
}
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
const { role, ...fields } = parsed;
|
|
183
|
-
if (role === "user") {
|
|
184
|
-
return agentHistoryItemSchema.parse({
|
|
185
|
-
...fields,
|
|
186
|
-
type: "user_message",
|
|
187
|
-
provenance
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
if (role === "assistant") {
|
|
191
|
-
return agentHistoryItemSchema.parse({
|
|
192
|
-
...fields,
|
|
193
|
-
type: "assistant_message"
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
if (role === "toolResult") {
|
|
197
|
-
return agentHistoryItemSchema.parse({
|
|
198
|
-
...fields,
|
|
199
|
-
type: "tool_result"
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
throw new Error(`Unsupported durable agent message role "${role}"`);
|
|
158
|
+
function estimateTextTokens(text) {
|
|
159
|
+
return Math.ceil(text.length / 4);
|
|
203
160
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const { type: _2, ...fields2 } = data;
|
|
211
|
-
return piMessageSchema.parse({ ...fields2, role: "assistant" });
|
|
212
|
-
}
|
|
213
|
-
const { type: _, ...fields } = data;
|
|
214
|
-
return piMessageSchema.parse({ ...fields, role: "toolResult" });
|
|
161
|
+
function calculateContextCompactionTriggerTokens(model) {
|
|
162
|
+
const contextWindow = positiveInteger(
|
|
163
|
+
model.contextWindow,
|
|
164
|
+
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
165
|
+
);
|
|
166
|
+
return Math.max(1, Math.floor(contextWindow * COMPACTION_TRIGGER_RATIO));
|
|
215
167
|
}
|
|
216
|
-
function
|
|
217
|
-
|
|
168
|
+
function calculateContextInputLimitTokens(model) {
|
|
169
|
+
const contextWindow = positiveInteger(
|
|
170
|
+
model.contextWindow,
|
|
171
|
+
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
172
|
+
);
|
|
173
|
+
return Math.max(1, Math.floor(contextWindow * CONTEXT_INPUT_LIMIT_RATIO));
|
|
218
174
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
const provenance = [];
|
|
222
|
-
const seqs = [];
|
|
223
|
-
let modelProfile = "standard";
|
|
224
|
-
let modelId;
|
|
225
|
-
for (const event of events) {
|
|
226
|
-
if (options?.maxSeq !== void 0 && event.seq > options.maxSeq) break;
|
|
227
|
-
if (event.data.type === "unknown") {
|
|
228
|
-
throw new Error(
|
|
229
|
-
`Unsupported conversation event "${event.data.originalType}" at seq ${event.seq} (schema version ${event.schemaVersion})`
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
if (event.data.type === "compaction" || event.data.type === "handoff") {
|
|
233
|
-
modelProfile = event.data.modelProfile;
|
|
234
|
-
modelId = event.data.modelId;
|
|
235
|
-
for (const replacement of event.data.replacementHistory) {
|
|
236
|
-
for (const message of durableMessages(
|
|
237
|
-
piMessageFromHistoryItem(replacement.item)
|
|
238
|
-
)) {
|
|
239
|
-
messages.push(message);
|
|
240
|
-
provenance.push(historyItemProvenance(replacement.item));
|
|
241
|
-
seqs.push(replacement.sourceEventSeq ?? event.seq);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
if (event.data.type === "user_message" || event.data.type === "assistant_message" || event.data.type === "tool_result") {
|
|
247
|
-
for (const message of durableMessages(
|
|
248
|
-
piMessageFromHistoryItem(event.data)
|
|
249
|
-
)) {
|
|
250
|
-
messages.push(message);
|
|
251
|
-
provenance.push(historyItemProvenance(event.data));
|
|
252
|
-
seqs.push(event.seq);
|
|
253
|
-
}
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
if (event.data.type === "authorization_completed") {
|
|
257
|
-
messages.push(
|
|
258
|
-
authorizationObservationMessage(event.data, event.createdAtMs)
|
|
259
|
-
);
|
|
260
|
-
provenance.push(contextProvenance);
|
|
261
|
-
seqs.push(event.seq);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return { messages, provenance, seqs, modelProfile, modelId };
|
|
175
|
+
function calculateContextCompactionTargetTokens(triggerTokens) {
|
|
176
|
+
return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
|
|
265
177
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
178
|
+
function getModelContextBudget(modelId) {
|
|
179
|
+
const model = resolveGatewayModel(modelId);
|
|
180
|
+
const advertisedContextWindow = positiveInteger(
|
|
181
|
+
model.contextWindow,
|
|
182
|
+
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
183
|
+
);
|
|
184
|
+
return {
|
|
185
|
+
contextWindow: Math.min(
|
|
186
|
+
botConfig.contextWindowTokens,
|
|
187
|
+
advertisedContextWindow
|
|
188
|
+
)
|
|
189
|
+
};
|
|
276
190
|
}
|
|
277
|
-
function
|
|
278
|
-
return
|
|
191
|
+
function getAgentContextCompactionTriggerTokens(modelId) {
|
|
192
|
+
return calculateContextCompactionTriggerTokens(
|
|
193
|
+
getModelContextBudget(modelId)
|
|
194
|
+
);
|
|
279
195
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
for (let index = 0; index < limit; index += 1) {
|
|
283
|
-
if (!isDeepStrictEqual(left[index], right[index])) {
|
|
284
|
-
return index;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return limit;
|
|
196
|
+
function getAgentContextInputLimitTokens(modelId) {
|
|
197
|
+
return calculateContextInputLimitTokens(getModelContextBudget(modelId));
|
|
288
198
|
}
|
|
289
|
-
function
|
|
290
|
-
return
|
|
291
|
-
|
|
199
|
+
function getConversationContextCompactionTriggerTokens() {
|
|
200
|
+
return calculateContextCompactionTriggerTokens(
|
|
201
|
+
getModelContextBudget(botConfig.fastModelId)
|
|
292
202
|
);
|
|
293
203
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
204
|
+
|
|
205
|
+
// src/chat/services/conversation-memory.ts
|
|
206
|
+
var CONTEXT_MIN_LIVE_MESSAGES = 12;
|
|
207
|
+
var CONTEXT_COMPACTION_BATCH_SIZE = 24;
|
|
208
|
+
var CONTEXT_MAX_COMPACTIONS = 16;
|
|
209
|
+
var CONTEXT_MAX_MESSAGE_CHARS = 3200;
|
|
210
|
+
var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
211
|
+
function generateConversationId(prefix) {
|
|
212
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
213
|
+
}
|
|
214
|
+
function normalizeConversationText(text) {
|
|
215
|
+
return text.replace(/\r\n?/g, "\n").trim();
|
|
216
|
+
}
|
|
217
|
+
function buildImageContextSuffix(message, conversation) {
|
|
218
|
+
const byFileId = conversation?.vision.byFileId;
|
|
219
|
+
const imageFileIds = message.meta?.imageFileIds ?? [];
|
|
220
|
+
if (!byFileId || imageFileIds.length === 0) {
|
|
221
|
+
return "";
|
|
300
222
|
}
|
|
301
|
-
|
|
302
|
-
|
|
223
|
+
const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
|
|
224
|
+
if (summaries.length === 0) {
|
|
225
|
+
return "";
|
|
303
226
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
227
|
+
return ` [image context: ${summaries.join(" | ")}]`;
|
|
228
|
+
}
|
|
229
|
+
function renderConversationMessageLine(message, conversation) {
|
|
230
|
+
const displayName = conversationAuthorDisplayName(message);
|
|
231
|
+
const text = message.text.replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS);
|
|
232
|
+
const markers = [];
|
|
233
|
+
if (message.meta?.replied === false) {
|
|
234
|
+
markers.push(
|
|
235
|
+
`assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
|
|
236
|
+
);
|
|
315
237
|
}
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
throw new Error(
|
|
319
|
-
"trailing commit provenance cannot exceed committed messages"
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
const newMessageCount = args.nextMessages.length - matchingPrefix;
|
|
323
|
-
if (args.trailingMessageProvenance.length > newMessageCount) {
|
|
324
|
-
throw new Error(
|
|
325
|
-
"trailing commit provenance must align to newly committed messages"
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
const start = provenance.length - args.trailingMessageProvenance.length;
|
|
329
|
-
args.trailingMessageProvenance.forEach((entry, offset) => {
|
|
330
|
-
provenance[start + offset] = entry;
|
|
331
|
-
});
|
|
238
|
+
if (message.meta?.explicitMention) {
|
|
239
|
+
markers.push("explicit mention");
|
|
332
240
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const events = await getConversationEventStore().loadCurrentHistory(
|
|
337
|
-
args.conversationId
|
|
338
|
-
);
|
|
339
|
-
return projectConversationEvents(events).messages;
|
|
241
|
+
const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
|
|
242
|
+
const imageContext = buildImageContextSuffix(message, conversation);
|
|
243
|
+
return `[${message.role}] ${displayName}: ${text}${imageContext}${markerSuffix}`;
|
|
340
244
|
}
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
);
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
async function openConversationProjection(args) {
|
|
348
|
-
const events = await getConversationEventStore().loadCurrentHistory(
|
|
349
|
-
args.conversationId
|
|
350
|
-
);
|
|
351
|
-
return projectConversationEvents(events);
|
|
245
|
+
function conversationAuthorDisplayName(message) {
|
|
246
|
+
const author = message.author;
|
|
247
|
+
const fullName = authorDisplayField(author?.fullName, author?.userId);
|
|
248
|
+
const userName = authorDisplayField(author?.userName, author?.userId);
|
|
249
|
+
return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
|
|
352
250
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (args.committedSeq < 0) {
|
|
356
|
-
return projectConversationEvents(
|
|
357
|
-
await eventStore.loadCurrentHistory(args.conversationId)
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
const historyEvents = await eventStore.loadHistoryContaining(
|
|
361
|
-
args.conversationId,
|
|
362
|
-
args.committedSeq,
|
|
363
|
-
args.includeTail ? void 0 : args.committedSeq
|
|
364
|
-
);
|
|
365
|
-
if (!historyEvents) {
|
|
251
|
+
function authorDisplayField(value, userId) {
|
|
252
|
+
if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
|
|
366
253
|
return void 0;
|
|
367
254
|
}
|
|
368
|
-
return
|
|
369
|
-
}
|
|
370
|
-
async function loadConnectedMcpProviders(args) {
|
|
371
|
-
const events = await getConversationEventStore().loadCurrentHistory(
|
|
372
|
-
args.conversationId
|
|
373
|
-
);
|
|
374
|
-
return connectedMcpProvidersFromEvents(events);
|
|
375
|
-
}
|
|
376
|
-
function messageTimestamp(message) {
|
|
377
|
-
const timestamp = message.timestamp;
|
|
378
|
-
return typeof timestamp === "number" ? timestamp : Date.now();
|
|
255
|
+
return value;
|
|
379
256
|
}
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
args.conversationId,
|
|
385
|
-
async () => executor.transaction(
|
|
386
|
-
async () => await commitMessagesLocked(args, executor)
|
|
387
|
-
)
|
|
257
|
+
function updateConversationStats(conversation) {
|
|
258
|
+
const contextText = buildConversationContext(conversation);
|
|
259
|
+
conversation.stats.estimatedContextTokens = estimateTextTokens(
|
|
260
|
+
contextText ?? ""
|
|
388
261
|
);
|
|
262
|
+
conversation.stats.totalMessageCount = conversation.messages.length;
|
|
263
|
+
conversation.stats.updatedAtMs = Date.now();
|
|
389
264
|
}
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
args.conversationId
|
|
394
|
-
);
|
|
395
|
-
const current = projectConversationEvents(currentEvents);
|
|
396
|
-
const nextLocalMessages = stripRuntimeTurnContext(args.messages).map(
|
|
397
|
-
normalizeDurableMessage
|
|
398
|
-
);
|
|
399
|
-
const matchingPrefix = countMatchingPrefix(
|
|
400
|
-
current.messages,
|
|
401
|
-
nextLocalMessages
|
|
265
|
+
function upsertConversationMessage(conversation, message) {
|
|
266
|
+
const existingIndex = conversation.messages.findIndex(
|
|
267
|
+
(entry) => entry.id === message.id
|
|
402
268
|
);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
});
|
|
411
|
-
if (matchingPrefix === current.messages.length) {
|
|
412
|
-
const newMessages = nextLocalMessages.slice(matchingPrefix);
|
|
413
|
-
const turnContext = args.turnContext;
|
|
414
|
-
const turnContextEvents = turnContext?.contexts.map((context, index) => ({
|
|
415
|
-
idempotencyKey: `turn:${turnContext.turnId}:context:${context.pluginName}:${index}`,
|
|
416
|
-
createdAtMs: context.loadedAtMs,
|
|
417
|
-
data: {
|
|
418
|
-
type: "turn_context",
|
|
419
|
-
turnId: turnContext.turnId,
|
|
420
|
-
pluginName: context.pluginName,
|
|
421
|
-
kind: context.kind,
|
|
422
|
-
version: context.version,
|
|
423
|
-
content: context.content
|
|
269
|
+
if (existingIndex >= 0) {
|
|
270
|
+
conversation.messages[existingIndex] = {
|
|
271
|
+
...conversation.messages[existingIndex],
|
|
272
|
+
...message,
|
|
273
|
+
meta: {
|
|
274
|
+
...conversation.messages[existingIndex]?.meta,
|
|
275
|
+
...message.meta
|
|
424
276
|
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
data: historyItemFromPiMessage(
|
|
429
|
-
message,
|
|
430
|
-
nextLocalProvenance[matchingPrefix + index]
|
|
431
|
-
),
|
|
432
|
-
createdAtMs: messageTimestamp(message)
|
|
433
|
-
})),
|
|
434
|
-
...turnContextEvents
|
|
435
|
-
]);
|
|
436
|
-
} else {
|
|
437
|
-
throw new Error(
|
|
438
|
-
`Agent history for ${args.conversationId} changed before its committed boundary`
|
|
439
|
-
);
|
|
277
|
+
};
|
|
278
|
+
updateConversationStats(conversation);
|
|
279
|
+
return message.id;
|
|
440
280
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const committed = projectConversationEvents(committedEvents);
|
|
445
|
-
return {
|
|
446
|
-
committedSeq: committedEvents.at(-1)?.seq ?? -1,
|
|
447
|
-
historyVersion: committedEvents.at(-1)?.historyVersion ?? 0,
|
|
448
|
-
messageSeqs: committed.seqs,
|
|
449
|
-
messages: nextLocalMessages,
|
|
450
|
-
provenance: nextLocalProvenance
|
|
451
|
-
};
|
|
281
|
+
conversation.messages.push(message);
|
|
282
|
+
updateConversationStats(conversation);
|
|
283
|
+
return message.id;
|
|
452
284
|
}
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
285
|
+
function recordDeliveredAssistantMessage(args) {
|
|
286
|
+
const prefix = `${args.sessionId}:assistant:`;
|
|
287
|
+
const ordinal = args.conversation.messages.filter(
|
|
288
|
+
(message) => message.id.startsWith(prefix)
|
|
289
|
+
).length + 1;
|
|
290
|
+
const messageId = `${prefix}${ordinal}`;
|
|
291
|
+
markConversationMessage(args.conversation, args.userMessageId, {
|
|
292
|
+
replied: true,
|
|
293
|
+
skippedReason: void 0
|
|
294
|
+
});
|
|
295
|
+
upsertConversationMessage(args.conversation, {
|
|
296
|
+
id: messageId,
|
|
297
|
+
role: "assistant",
|
|
298
|
+
text: normalizeConversationText(args.text),
|
|
299
|
+
createdAtMs: Date.now(),
|
|
300
|
+
author: {
|
|
301
|
+
userName: botConfig.userName,
|
|
302
|
+
isBot: true
|
|
303
|
+
},
|
|
304
|
+
meta: {
|
|
305
|
+
replied: true
|
|
463
306
|
}
|
|
464
|
-
|
|
307
|
+
});
|
|
308
|
+
return messageId;
|
|
465
309
|
}
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
)) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
await eventStore.append(args.conversationId, [
|
|
475
|
-
{
|
|
476
|
-
data: {
|
|
477
|
-
type: "authorization_requested",
|
|
478
|
-
kind: args.kind,
|
|
479
|
-
provider: args.provider,
|
|
480
|
-
actorId: args.actorId,
|
|
481
|
-
authorizationId: args.authorizationId,
|
|
482
|
-
delivery: args.delivery
|
|
483
|
-
},
|
|
484
|
-
createdAtMs: Date.now()
|
|
485
|
-
}
|
|
486
|
-
]);
|
|
310
|
+
function turnHasReply(conversation, turnId) {
|
|
311
|
+
const assistantPrefix = `${turnId}:assistant:`;
|
|
312
|
+
return conversation.messages.some(
|
|
313
|
+
(message) => message.role === "assistant" && message.id.startsWith(assistantPrefix)
|
|
314
|
+
);
|
|
487
315
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
{
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
kind: args.kind,
|
|
501
|
-
provider: args.provider,
|
|
502
|
-
actorId: args.actorId,
|
|
503
|
-
authorizationId: args.authorizationId
|
|
504
|
-
},
|
|
505
|
-
createdAtMs: Date.now()
|
|
316
|
+
function markConversationMessage(conversation, messageId, patch) {
|
|
317
|
+
if (!messageId) return;
|
|
318
|
+
const messageIndex = conversation.messages.findIndex(
|
|
319
|
+
(entry) => entry.id === messageId
|
|
320
|
+
);
|
|
321
|
+
if (messageIndex < 0) return;
|
|
322
|
+
const current = conversation.messages[messageIndex];
|
|
323
|
+
conversation.messages[messageIndex] = {
|
|
324
|
+
...current,
|
|
325
|
+
meta: {
|
|
326
|
+
...current.meta ?? {},
|
|
327
|
+
...patch
|
|
506
328
|
}
|
|
507
|
-
|
|
329
|
+
};
|
|
330
|
+
updateConversationStats(conversation);
|
|
508
331
|
}
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
`turn:${args.turnId}:routed`
|
|
332
|
+
function buildConversationContext(conversation, options = {}) {
|
|
333
|
+
const messages = conversation.messages.filter(
|
|
334
|
+
(entry) => entry.id !== options.excludeMessageId
|
|
513
335
|
);
|
|
514
|
-
if (
|
|
336
|
+
if (messages.length === 0 && conversation.compactions.length === 0) {
|
|
515
337
|
return void 0;
|
|
516
338
|
}
|
|
517
|
-
|
|
518
|
-
|
|
339
|
+
const lines = [];
|
|
340
|
+
if (conversation.compactions.length > 0) {
|
|
341
|
+
lines.push("<thread-compactions>");
|
|
342
|
+
for (const [index, compaction] of conversation.compactions.entries()) {
|
|
343
|
+
lines.push(
|
|
344
|
+
` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageCount}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
|
|
345
|
+
compaction.summary,
|
|
346
|
+
" </compaction>"
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
lines.push("</thread-compactions>");
|
|
519
350
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
await getConversationEventStore().append(args.conversationId, [
|
|
524
|
-
{
|
|
525
|
-
idempotencyKey: `turn:${args.turnId}:routed`,
|
|
526
|
-
createdAtMs: Date.now(),
|
|
527
|
-
data: {
|
|
528
|
-
type: "turn_routed",
|
|
529
|
-
turnId: args.turnId,
|
|
530
|
-
modelProfile: args.modelProfile,
|
|
531
|
-
modelId: args.modelId,
|
|
532
|
-
reasoningLevel: args.reasoningLevel,
|
|
533
|
-
...args.confidence !== void 0 ? { confidence: args.confidence } : {},
|
|
534
|
-
source: args.source
|
|
535
|
-
}
|
|
351
|
+
if (messages.length > 0) {
|
|
352
|
+
if (lines.length > 0) {
|
|
353
|
+
lines.push("");
|
|
536
354
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
355
|
+
lines.push("<thread-transcript>");
|
|
356
|
+
for (const [index, message] of messages.entries()) {
|
|
357
|
+
const author = escapeXml(conversationAuthorDisplayName(message));
|
|
358
|
+
const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
|
|
359
|
+
const ts = new Date(message.createdAtMs).toISOString();
|
|
360
|
+
const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
|
|
361
|
+
lines.push(
|
|
362
|
+
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
|
|
363
|
+
renderConversationMessageLine(message, conversation),
|
|
364
|
+
" </message>"
|
|
365
|
+
);
|
|
548
366
|
}
|
|
549
|
-
|
|
367
|
+
lines.push("</thread-transcript>");
|
|
368
|
+
}
|
|
369
|
+
return lines.join("\n");
|
|
550
370
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
371
|
+
function pruneCompactions(compactions) {
|
|
372
|
+
if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
|
|
373
|
+
return compactions;
|
|
374
|
+
}
|
|
375
|
+
const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
|
|
376
|
+
const merged = compactions.slice(0, overflowCount);
|
|
377
|
+
const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
|
|
378
|
+
const coveredMessageCount = merged.reduce(
|
|
379
|
+
(count, entry) => count + entry.coveredMessageCount,
|
|
380
|
+
0
|
|
381
|
+
);
|
|
382
|
+
const compacted = {
|
|
383
|
+
id: generateConversationId("compaction"),
|
|
384
|
+
createdAtMs: Date.now(),
|
|
385
|
+
summary: mergedSummary,
|
|
386
|
+
coveredMessageCount
|
|
387
|
+
};
|
|
388
|
+
return [compacted, ...compactions.slice(overflowCount)];
|
|
556
389
|
}
|
|
557
|
-
function
|
|
558
|
-
|
|
390
|
+
async function summarizeConversationChunk(messages, conversation, context, deps) {
|
|
391
|
+
const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
|
|
392
|
+
try {
|
|
393
|
+
const result = await deps.completeText({
|
|
394
|
+
modelId: botConfig.fastModelId,
|
|
395
|
+
temperature: 0,
|
|
396
|
+
messages: [
|
|
397
|
+
{
|
|
398
|
+
role: "user",
|
|
399
|
+
content: [
|
|
400
|
+
"Summarize the following older Slack thread transcript segment for future assistant turns.",
|
|
401
|
+
"Keep the summary factual and concise. Do not invent details.",
|
|
402
|
+
"",
|
|
403
|
+
"Output exactly three XML sections in this order:",
|
|
404
|
+
"<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>",
|
|
405
|
+
"<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>",
|
|
406
|
+
"<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>",
|
|
407
|
+
"",
|
|
408
|
+
"Do not output any text outside the three sections.",
|
|
409
|
+
"",
|
|
410
|
+
transcript
|
|
411
|
+
].join("\n"),
|
|
412
|
+
timestamp: Date.now()
|
|
413
|
+
}
|
|
414
|
+
],
|
|
415
|
+
metadata: {
|
|
416
|
+
modelId: botConfig.fastModelId,
|
|
417
|
+
threadId: context.threadId ?? "",
|
|
418
|
+
channelId: context.channelId ?? "",
|
|
419
|
+
actorId: context.actorId ?? "",
|
|
420
|
+
runId: context.runId ?? ""
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
const summary = result.text.trim();
|
|
424
|
+
if (summary.length > 0) {
|
|
425
|
+
return summary.slice(0, 3500);
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
logWarn("conversation.compaction.summary.failed", {
|
|
429
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
430
|
+
"app.compaction_messages_covered": messages.length
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return transcript.slice(0, 2800);
|
|
559
434
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
constructor(args) {
|
|
566
|
-
super(
|
|
567
|
-
args.cause instanceof Error ? args.cause.message : String(args.cause),
|
|
435
|
+
async function generateThreadTitleWithDeps(sourceText, deps) {
|
|
436
|
+
const result = await deps.completeText({
|
|
437
|
+
modelId: botConfig.fastModelId,
|
|
438
|
+
temperature: 0,
|
|
439
|
+
messages: [
|
|
568
440
|
{
|
|
569
|
-
|
|
441
|
+
role: "user",
|
|
442
|
+
content: [
|
|
443
|
+
"Generate a concise 5-8 word Slack conversation title from the first user message below.",
|
|
444
|
+
"Capture the user's main request.",
|
|
445
|
+
"Reply with ONLY the title, with no quotes or trailing punctuation.",
|
|
446
|
+
"",
|
|
447
|
+
`First user message: ${sourceText.slice(0, 500)}`
|
|
448
|
+
].join("\n"),
|
|
449
|
+
timestamp: Date.now()
|
|
570
450
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function getConversationTurnBoundaryError(error) {
|
|
578
|
-
return error instanceof ConversationTurnBoundaryError ? error : void 0;
|
|
451
|
+
],
|
|
452
|
+
metadata: {
|
|
453
|
+
modelId: botConfig.fastModelId
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return result.text.trim().slice(0, 60);
|
|
579
457
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
458
|
+
function getThreadTitleSourceMessage(conversation) {
|
|
459
|
+
let firstMessage;
|
|
460
|
+
for (const message of conversation.messages) {
|
|
461
|
+
if (!isHumanConversationMessage(message)) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (!firstMessage) {
|
|
465
|
+
firstMessage = message;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (message.createdAtMs < firstMessage.createdAtMs) {
|
|
469
|
+
firstMessage = message;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
|
|
473
|
+
firstMessage = message;
|
|
474
|
+
}
|
|
585
475
|
}
|
|
586
|
-
|
|
587
|
-
function isCooperativeTurnYieldError(error) {
|
|
588
|
-
return error instanceof CooperativeTurnYieldError;
|
|
476
|
+
return firstMessage;
|
|
589
477
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
478
|
+
async function compactConversationIfNeededWithDeps(conversation, context, deps) {
|
|
479
|
+
updateConversationStats(conversation);
|
|
480
|
+
let estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
481
|
+
setSpanAttributes({
|
|
482
|
+
"app.context_tokens_estimated": estimatedTokens
|
|
483
|
+
});
|
|
484
|
+
const triggerTokens = getConversationContextCompactionTriggerTokens();
|
|
485
|
+
const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
|
|
486
|
+
while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
|
|
487
|
+
const compactCount = Math.min(
|
|
488
|
+
CONTEXT_COMPACTION_BATCH_SIZE,
|
|
489
|
+
conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
|
|
490
|
+
);
|
|
491
|
+
if (compactCount <= 0) {
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
const compactedChunk = conversation.messages.slice(0, compactCount);
|
|
495
|
+
const summary = await summarizeConversationChunk(
|
|
496
|
+
compactedChunk,
|
|
497
|
+
conversation,
|
|
498
|
+
context,
|
|
499
|
+
deps
|
|
500
|
+
);
|
|
501
|
+
conversation.compactions.push({
|
|
502
|
+
id: generateConversationId("compaction"),
|
|
503
|
+
createdAtMs: Date.now(),
|
|
504
|
+
summary,
|
|
505
|
+
coveredMessageCount: compactedChunk.length
|
|
506
|
+
});
|
|
507
|
+
conversation.compactions = pruneCompactions(conversation.compactions);
|
|
508
|
+
conversation.messages = conversation.messages.slice(compactCount);
|
|
509
|
+
conversation.stats.compactedMessageCount += compactCount;
|
|
510
|
+
updateConversationStats(conversation);
|
|
511
|
+
estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
512
|
+
setSpanAttributes({
|
|
513
|
+
"app.compaction_messages_covered": compactCount,
|
|
514
|
+
"app.compaction.trigger_tokens": triggerTokens,
|
|
515
|
+
"app.compaction.target_tokens": targetTokens,
|
|
516
|
+
"app.context_tokens_estimated": estimatedTokens
|
|
517
|
+
});
|
|
518
|
+
if (estimatedTokens <= targetTokens) {
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
595
521
|
}
|
|
596
|
-
};
|
|
597
|
-
function isTurnInputCommitLostError(error) {
|
|
598
|
-
return error instanceof TurnInputCommitLostError;
|
|
599
522
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
function isTurnInputDeferredError(error) {
|
|
608
|
-
return error instanceof TurnInputDeferredError;
|
|
523
|
+
function createConversationMemoryService(deps) {
|
|
524
|
+
return {
|
|
525
|
+
compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps),
|
|
526
|
+
generateThreadTitle: async (sourceText) => await generateThreadTitleWithDeps(sourceText, deps)
|
|
527
|
+
};
|
|
609
528
|
}
|
|
610
|
-
function
|
|
611
|
-
|
|
612
|
-
args.updateConversationStats(args.conversation);
|
|
529
|
+
function isHumanConversationMessage(message) {
|
|
530
|
+
return message.role === "user" && message.author?.isBot !== true;
|
|
613
531
|
}
|
|
614
|
-
function
|
|
615
|
-
|
|
616
|
-
|
|
532
|
+
function getConversationMessageSlackTs(message) {
|
|
533
|
+
return parseSlackMessageTs(message.meta?.slackTs);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/chat/conversations/messages.ts
|
|
537
|
+
import { createHash } from "crypto";
|
|
538
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
539
|
+
|
|
540
|
+
// src/chat/conversations/message-summaries.ts
|
|
541
|
+
import { isDeepStrictEqual } from "util";
|
|
542
|
+
function projectConversationMessageSummaries(events) {
|
|
543
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
544
|
+
const data = events[index]?.data;
|
|
545
|
+
if (data?.type === "messages_summarized") {
|
|
546
|
+
return data.compactions;
|
|
547
|
+
}
|
|
617
548
|
}
|
|
549
|
+
return [];
|
|
618
550
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
551
|
+
async function persistConversationMessageSummaries(args) {
|
|
552
|
+
const eventStore = getConversationEventStore();
|
|
553
|
+
const history = await eventStore.loadMessageHistory(args.conversationId);
|
|
554
|
+
const existing = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
|
|
555
|
+
if (isDeepStrictEqual(existing, args.conversation.compactions)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const liveMessageIds = new Set(
|
|
559
|
+
args.conversation.messages.map((message) => message.id)
|
|
560
|
+
);
|
|
561
|
+
const historyFromSeq = history.events.find(
|
|
562
|
+
(event) => event.data.type === "message" && liveMessageIds.has(event.data.messageId)
|
|
563
|
+
)?.seq;
|
|
564
|
+
const nextHistoryFromSeq = historyFromSeq ?? Math.max(history.historyFromSeq, (history.events.at(-1)?.seq ?? -1) + 1);
|
|
565
|
+
await eventStore.append(args.conversationId, [
|
|
566
|
+
{
|
|
567
|
+
data: {
|
|
568
|
+
type: "messages_summarized",
|
|
569
|
+
historyFromSeq: nextHistoryFromSeq,
|
|
570
|
+
compactions: args.conversation.compactions
|
|
571
|
+
},
|
|
572
|
+
createdAtMs: args.conversation.compactions.at(-1)?.createdAtMs ?? Date.now()
|
|
573
|
+
}
|
|
574
|
+
]);
|
|
623
575
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
576
|
+
|
|
577
|
+
// src/chat/conversations/message-projection.ts
|
|
578
|
+
function isMessageEvent(event) {
|
|
579
|
+
return event.data.type === "message" || event.data.type === "message_updated" || event.data.type === "message_handled";
|
|
628
580
|
}
|
|
629
|
-
function
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
581
|
+
function splitMeta(meta) {
|
|
582
|
+
const rest = { ...meta ?? {} };
|
|
583
|
+
const author = rest.author;
|
|
584
|
+
delete rest.author;
|
|
585
|
+
return {
|
|
586
|
+
...author ? { author } : {},
|
|
587
|
+
...Object.keys(rest).length > 0 ? { meta: rest } : {}
|
|
588
|
+
};
|
|
637
589
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
590
|
+
function missingBaselineError(event) {
|
|
591
|
+
return new Error(
|
|
592
|
+
`Message event ${event.data.type} at seq ${event.seq} references ${event.data.messageId} before message`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
function projectConversationMessages(history) {
|
|
596
|
+
const byId = /* @__PURE__ */ new Map();
|
|
597
|
+
for (const event of history.events) {
|
|
598
|
+
if (!isMessageEvent(event)) continue;
|
|
599
|
+
const data = event.data;
|
|
600
|
+
const current = byId.get(data.messageId);
|
|
601
|
+
if (data.type === "message" || data.type === "message_updated") {
|
|
602
|
+
const message = {
|
|
603
|
+
id: data.messageId,
|
|
604
|
+
role: data.role,
|
|
605
|
+
text: data.text,
|
|
606
|
+
createdAtMs: event.createdAtMs,
|
|
607
|
+
...splitMeta(data.meta)
|
|
608
|
+
};
|
|
609
|
+
if (data.type === "message" && current)
|
|
610
|
+
throw new Error(
|
|
611
|
+
`Duplicate message event for ${data.messageId} at seq ${event.seq}`
|
|
612
|
+
);
|
|
613
|
+
if (data.type === "message_updated" && !current) {
|
|
614
|
+
if (history.historyFromSeq > 0) continue;
|
|
615
|
+
throw missingBaselineError(event);
|
|
651
616
|
}
|
|
617
|
+
byId.set(data.messageId, {
|
|
618
|
+
message: {
|
|
619
|
+
...message,
|
|
620
|
+
createdAtMs: current?.message.createdAtMs ?? message.createdAtMs
|
|
621
|
+
},
|
|
622
|
+
...current?.repliedAtMs === void 0 ? {} : { repliedAtMs: current.repliedAtMs }
|
|
623
|
+
});
|
|
624
|
+
continue;
|
|
652
625
|
}
|
|
626
|
+
if (!current) {
|
|
627
|
+
if (history.historyFromSeq > 0) continue;
|
|
628
|
+
throw missingBaselineError(event);
|
|
629
|
+
}
|
|
630
|
+
current.repliedAtMs ??= event.createdAtMs;
|
|
653
631
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
destinationSchema,
|
|
662
|
-
sourceSchema
|
|
663
|
-
} from "@sentry/junior-plugin-api";
|
|
664
|
-
import { z as z2 } from "zod";
|
|
665
|
-
|
|
666
|
-
// src/chat/state/turn-session-keys.ts
|
|
667
|
-
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
668
|
-
function agentTurnSessionKey(conversationId, sessionId) {
|
|
669
|
-
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
670
|
-
}
|
|
671
|
-
function agentTurnSessionConversationIndexKey(conversationId) {
|
|
672
|
-
return `${AGENT_TURN_SESSION_PREFIX}:conversation:${conversationId}:index`;
|
|
632
|
+
return [...byId.values()].map(({ message, repliedAtMs }) => {
|
|
633
|
+
if (repliedAtMs === void 0) return message;
|
|
634
|
+
return {
|
|
635
|
+
...message,
|
|
636
|
+
meta: { ...message.meta ?? {}, replied: true }
|
|
637
|
+
};
|
|
638
|
+
});
|
|
673
639
|
}
|
|
674
640
|
|
|
675
|
-
// src/chat/
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
"completed",
|
|
683
|
-
"failed",
|
|
684
|
-
"abandoned"
|
|
685
|
-
]);
|
|
686
|
-
var agentTurnSurfaceSchema = z2.enum([
|
|
687
|
-
"slack",
|
|
688
|
-
"api",
|
|
689
|
-
"scheduler",
|
|
690
|
-
"internal"
|
|
691
|
-
]);
|
|
692
|
-
var agentTurnResumeReasonSchema = z2.enum([
|
|
693
|
-
"timeout",
|
|
694
|
-
"auth",
|
|
695
|
-
"yield",
|
|
696
|
-
"retry"
|
|
697
|
-
]);
|
|
698
|
-
var nonNegativeNumberSchema = z2.number().finite().nonnegative();
|
|
699
|
-
var seqCursorSchema = z2.number().int().min(-1);
|
|
700
|
-
var agentTurnSessionSummarySchema = z2.object({
|
|
701
|
-
channelName: z2.string().min(1).optional(),
|
|
702
|
-
version: z2.number().int().nonnegative(),
|
|
703
|
-
conversationId: z2.string().min(1),
|
|
704
|
-
cumulativeDurationMs: nonNegativeNumberSchema,
|
|
705
|
-
cumulativeUsage: agentTurnUsageSchema.optional(),
|
|
706
|
-
destination: destinationSchema.optional(),
|
|
707
|
-
dispatchId: z2.string().min(1).optional(),
|
|
708
|
-
dispatchOutcome: z2.enum(["blocked", "completed", "failed"]).optional(),
|
|
709
|
-
resultMessageId: z2.string().min(1).optional(),
|
|
710
|
-
source: sourceSchema.optional(),
|
|
711
|
-
lastProgressAtMs: nonNegativeNumberSchema,
|
|
712
|
-
loadedSkillNames: z2.array(z2.string()).optional(),
|
|
713
|
-
modelId: z2.string().min(1).optional(),
|
|
714
|
-
reasoningLevel: z2.string().min(1).optional(),
|
|
715
|
-
actor: actorSchema.optional(),
|
|
716
|
-
resumeReason: agentTurnResumeReasonSchema.optional(),
|
|
717
|
-
resumedFromSliceId: z2.number().int().nonnegative().optional(),
|
|
718
|
-
sessionId: z2.string().min(1),
|
|
719
|
-
sliceId: z2.number().int().nonnegative(),
|
|
720
|
-
startedAtMs: nonNegativeNumberSchema,
|
|
721
|
-
state: agentTurnSessionStatusSchema,
|
|
722
|
-
surface: agentTurnSurfaceSchema.optional(),
|
|
723
|
-
traceId: z2.string().optional(),
|
|
724
|
-
updatedAtMs: nonNegativeNumberSchema
|
|
725
|
-
}).strict();
|
|
726
|
-
var storedAgentTurnSessionRecordSchema = agentTurnSessionSummarySchema.extend({
|
|
727
|
-
actors: z2.array(actorSchema).optional(),
|
|
728
|
-
committedSeq: seqCursorSchema,
|
|
729
|
-
historyVersion: z2.number().int().nonnegative().optional(),
|
|
730
|
-
errorMessage: z2.string().optional(),
|
|
731
|
-
turnStartSeq: seqCursorSchema.optional(),
|
|
732
|
-
runtimeContext: z2.array(piMessageSchema).optional()
|
|
733
|
-
}).strict();
|
|
734
|
-
function conversationExecutionFromSummary(summary) {
|
|
735
|
-
const status = summary.state === "completed" || summary.state === "abandoned" ? "idle" : summary.state;
|
|
641
|
+
// src/chat/conversations/messages.ts
|
|
642
|
+
function toStoredConversationMessage(message) {
|
|
643
|
+
const meta = {};
|
|
644
|
+
if (message.author) meta.author = message.author;
|
|
645
|
+
const { replied, ...restMeta } = message.meta ?? {};
|
|
646
|
+
Object.assign(meta, restMeta);
|
|
647
|
+
if (replied === false) meta.replied = false;
|
|
736
648
|
return {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
649
|
+
messageId: message.id,
|
|
650
|
+
role: message.role,
|
|
651
|
+
text: message.text,
|
|
652
|
+
...Object.keys(meta).length > 0 ? { meta } : {},
|
|
653
|
+
createdAtMs: message.createdAtMs
|
|
740
654
|
};
|
|
741
655
|
}
|
|
742
|
-
function
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
656
|
+
function canonicalJson(value) {
|
|
657
|
+
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
658
|
+
if (!value || typeof value !== "object") return value;
|
|
659
|
+
return Object.fromEntries(
|
|
660
|
+
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson(item)])
|
|
661
|
+
);
|
|
747
662
|
}
|
|
748
|
-
function
|
|
749
|
-
return
|
|
663
|
+
function messageStateKey(message) {
|
|
664
|
+
return createHash("sha256").update(JSON.stringify(canonicalJson(message))).digest("hex").slice(0, 32);
|
|
750
665
|
}
|
|
751
|
-
async function
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
)
|
|
763
|
-
]);
|
|
666
|
+
async function hydrateConversationMessages(args) {
|
|
667
|
+
if (!args.conversationId) {
|
|
668
|
+
args.conversation.messages = [];
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const history = await getConversationEventStore().loadMessageHistory(
|
|
672
|
+
args.conversationId
|
|
673
|
+
);
|
|
674
|
+
args.conversation.compactions = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
|
|
675
|
+
args.conversation.messages = projectConversationMessages(history);
|
|
676
|
+
updateConversationStats(args.conversation);
|
|
764
677
|
}
|
|
765
|
-
async function
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
channelName: args.summary.channelName,
|
|
771
|
-
conversationId: args.summary.conversationId,
|
|
772
|
-
destination: args.summary.destination,
|
|
773
|
-
nowMs: args.nowMs,
|
|
774
|
-
actor: sessionLogActor(args.summary.actor),
|
|
775
|
-
source,
|
|
776
|
-
visibility: args.destinationVisibility
|
|
678
|
+
async function persistConversationMessages(args) {
|
|
679
|
+
if (!args.conversationId || args.conversation.messages.length === 0) return;
|
|
680
|
+
await appendConversationMessages(getConversationEventStore(), {
|
|
681
|
+
...args,
|
|
682
|
+
conversationId: args.conversationId
|
|
777
683
|
});
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
684
|
+
}
|
|
685
|
+
async function appendConversationMessages(eventStore, args) {
|
|
686
|
+
if (args.conversation.messages.length === 0) return;
|
|
687
|
+
const history = await eventStore.loadMessageHistory(args.conversationId);
|
|
688
|
+
const existingMessages = new Map(
|
|
689
|
+
projectConversationMessages(history).map((message) => [
|
|
690
|
+
message.id,
|
|
691
|
+
message
|
|
692
|
+
])
|
|
693
|
+
);
|
|
694
|
+
const handledAtMs = args.repliedAtMs ?? Date.now();
|
|
695
|
+
const events = args.conversation.messages.flatMap((message) => {
|
|
696
|
+
const stored = toStoredConversationMessage(message);
|
|
697
|
+
const existing = existingMessages.get(stored.messageId);
|
|
698
|
+
const changed = existing !== void 0 && !isDeepStrictEqual2(toStoredConversationMessage(existing), stored);
|
|
699
|
+
const messageEvent = existing ? changed ? [
|
|
700
|
+
{
|
|
701
|
+
idempotencyKey: `message:${stored.messageId}:update:${messageStateKey(stored)}`,
|
|
702
|
+
createdAtMs: handledAtMs,
|
|
703
|
+
data: {
|
|
704
|
+
type: "message_updated",
|
|
705
|
+
messageId: stored.messageId,
|
|
706
|
+
role: stored.role,
|
|
707
|
+
text: stored.text,
|
|
708
|
+
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
|
|
709
|
+
...stored.meta ? { meta: stored.meta } : {}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
] : [] : [
|
|
713
|
+
{
|
|
714
|
+
idempotencyKey: `message:${stored.messageId}`,
|
|
715
|
+
createdAtMs: stored.createdAtMs,
|
|
716
|
+
data: {
|
|
717
|
+
type: "message",
|
|
718
|
+
messageId: stored.messageId,
|
|
719
|
+
role: stored.role,
|
|
720
|
+
text: stored.text,
|
|
721
|
+
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
|
|
722
|
+
...stored.meta ? { meta: stored.meta } : {}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
];
|
|
726
|
+
return [
|
|
727
|
+
...messageEvent,
|
|
728
|
+
...message.meta?.replied === true ? [
|
|
729
|
+
{
|
|
730
|
+
idempotencyKey: `message:${stored.messageId}:handled`,
|
|
731
|
+
createdAtMs: handledAtMs,
|
|
732
|
+
data: {
|
|
733
|
+
type: "message_handled",
|
|
734
|
+
messageId: stored.messageId
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
] : []
|
|
738
|
+
];
|
|
793
739
|
});
|
|
740
|
+
await eventStore.append(args.conversationId, events);
|
|
794
741
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
...stored.actor ? { actor: stored.actor } : {},
|
|
823
|
-
...stored.resumedFromSliceId !== void 0 ? { resumedFromSliceId: stored.resumedFromSliceId } : {},
|
|
824
|
-
...stored.surface ? { surface: stored.surface } : {},
|
|
825
|
-
...stored.traceId ? { traceId: stored.traceId } : {},
|
|
826
|
-
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {}
|
|
827
|
-
};
|
|
742
|
+
|
|
743
|
+
// src/chat/conversations/projection.ts
|
|
744
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
745
|
+
|
|
746
|
+
// src/chat/pi/messages.ts
|
|
747
|
+
import { z } from "zod";
|
|
748
|
+
var piMessageSchema = z.object({
|
|
749
|
+
role: z.string()
|
|
750
|
+
}).passthrough().transform((value) => value);
|
|
751
|
+
var piContentMessageSchema = z.object({
|
|
752
|
+
content: z.array(z.unknown()),
|
|
753
|
+
role: z.string().min(1)
|
|
754
|
+
}).passthrough().transform((value) => value);
|
|
755
|
+
|
|
756
|
+
// src/chat/pi/conversation-events.ts
|
|
757
|
+
function authorizationObservationMessage(data, createdAtMs) {
|
|
758
|
+
const label = data.kind === "mcp" ? "MCP authorization" : "Authorization";
|
|
759
|
+
return piMessageSchema.parse({
|
|
760
|
+
role: "user",
|
|
761
|
+
content: [
|
|
762
|
+
{
|
|
763
|
+
type: "text",
|
|
764
|
+
text: `${label} completed for provider "${data.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
765
|
+
}
|
|
766
|
+
],
|
|
767
|
+
timestamp: createdAtMs
|
|
768
|
+
});
|
|
828
769
|
}
|
|
829
|
-
function
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
770
|
+
function durableMessages(message) {
|
|
771
|
+
return stripRuntimeTurnContext([message]);
|
|
772
|
+
}
|
|
773
|
+
function historyItemFromPiMessage(message, provenance) {
|
|
774
|
+
const parsed = piMessageSchema.parse(message);
|
|
775
|
+
const { role, ...fields } = parsed;
|
|
776
|
+
if (role === "user") {
|
|
777
|
+
return agentHistoryItemSchema.parse({
|
|
778
|
+
...fields,
|
|
779
|
+
type: "user_message",
|
|
780
|
+
provenance
|
|
838
781
|
});
|
|
839
|
-
if (targetIndex < 0) {
|
|
840
|
-
unmatchedRuntimeContext.push(runtimeMessage);
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
const target = restored[targetIndex];
|
|
844
|
-
restored[targetIndex] = {
|
|
845
|
-
...restored[targetIndex],
|
|
846
|
-
content: [
|
|
847
|
-
...Array.isArray(runtime.content) ? runtime.content : [],
|
|
848
|
-
...Array.isArray(target.content) ? target.content : []
|
|
849
|
-
]
|
|
850
|
-
};
|
|
851
782
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
const value = await stateAdapter.get(
|
|
858
|
-
agentTurnSessionKey(conversationId, sessionId)
|
|
859
|
-
);
|
|
860
|
-
return value == null ? void 0 : parseAgentTurnSessionRecord(value);
|
|
861
|
-
}
|
|
862
|
-
async function materializeStoredAgentTurnSessionRecord(conversationId, sessionId, followCurrentReplacement) {
|
|
863
|
-
const parsed = await getStoredAgentTurnSessionRecord(
|
|
864
|
-
conversationId,
|
|
865
|
-
sessionId
|
|
866
|
-
);
|
|
867
|
-
if (!parsed) {
|
|
868
|
-
return void 0;
|
|
783
|
+
if (role === "assistant") {
|
|
784
|
+
return agentHistoryItemSchema.parse({
|
|
785
|
+
...fields,
|
|
786
|
+
type: "assistant_message"
|
|
787
|
+
});
|
|
869
788
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
includeTail: parsed.state === "running" || parsed.state === "awaiting_resume"
|
|
876
|
-
});
|
|
877
|
-
if (!pinnedProjection) {
|
|
878
|
-
return void 0;
|
|
789
|
+
if (role === "toolResult") {
|
|
790
|
+
return agentHistoryItemSchema.parse({
|
|
791
|
+
...fields,
|
|
792
|
+
type: "tool_result"
|
|
793
|
+
});
|
|
879
794
|
}
|
|
880
|
-
|
|
881
|
-
const currentHistoryVersion = currentHistory.at(-1)?.historyVersion ?? parsed.historyVersion ?? 0;
|
|
882
|
-
const followsReplacement = followCurrentReplacement && (parsed.state === "running" || parsed.state === "awaiting_resume") && parsed.historyVersion !== void 0 && parsed.historyVersion !== currentHistoryVersion;
|
|
883
|
-
const piProjection = followsReplacement ? projectConversationEvents(currentHistory) : pinnedProjection;
|
|
884
|
-
const turnStartMessageIndex = followsReplacement ? 0 : parsed.turnStartSeq === void 0 ? void 0 : piProjection.seqs.filter((seq) => seq <= parsed.turnStartSeq).length;
|
|
885
|
-
return materializeAgentTurnSessionRecord(
|
|
886
|
-
parsed,
|
|
887
|
-
piProjection,
|
|
888
|
-
turnStartMessageIndex,
|
|
889
|
-
!followsReplacement && (parsed.historyVersion === void 0 || parsed.historyVersion === currentHistoryVersion)
|
|
890
|
-
);
|
|
795
|
+
throw new Error(`Unsupported durable agent message role "${role}"`);
|
|
891
796
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
)
|
|
797
|
+
function piMessageFromHistoryItem(data) {
|
|
798
|
+
if (data.type === "user_message") {
|
|
799
|
+
const { type: _2, provenance: __, ...fields2 } = data;
|
|
800
|
+
return piMessageSchema.parse({ ...fields2, role: "user" });
|
|
801
|
+
}
|
|
802
|
+
if (data.type === "assistant_message") {
|
|
803
|
+
const { type: _2, ...fields2 } = data;
|
|
804
|
+
return piMessageSchema.parse({ ...fields2, role: "assistant" });
|
|
805
|
+
}
|
|
806
|
+
const { type: _, ...fields } = data;
|
|
807
|
+
return piMessageSchema.parse({ ...fields, role: "toolResult" });
|
|
898
808
|
}
|
|
899
|
-
|
|
900
|
-
return
|
|
901
|
-
conversationId,
|
|
902
|
-
sessionId,
|
|
903
|
-
true
|
|
904
|
-
);
|
|
809
|
+
function historyItemProvenance(data) {
|
|
810
|
+
return data.type === "user_message" ? data.provenance : contextProvenance;
|
|
905
811
|
}
|
|
906
|
-
function
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
812
|
+
function projectConversationEvents(events, options) {
|
|
813
|
+
const messages = [];
|
|
814
|
+
const provenance = [];
|
|
815
|
+
const seqs = [];
|
|
816
|
+
let modelProfile = "standard";
|
|
817
|
+
let modelId;
|
|
818
|
+
for (const event of events) {
|
|
819
|
+
if (options?.maxSeq !== void 0 && event.seq > options.maxSeq) break;
|
|
820
|
+
if (event.data.type === "unknown") {
|
|
821
|
+
throw new Error(
|
|
822
|
+
`Unsupported conversation event "${event.data.originalType}" at seq ${event.seq} (schema version ${event.schemaVersion})`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
if (event.data.type === "compaction" || event.data.type === "handoff") {
|
|
826
|
+
modelProfile = event.data.modelProfile;
|
|
827
|
+
modelId = event.data.modelId;
|
|
828
|
+
for (const replacement of event.data.replacementHistory) {
|
|
829
|
+
for (const message of durableMessages(
|
|
830
|
+
piMessageFromHistoryItem(replacement.item)
|
|
831
|
+
)) {
|
|
832
|
+
messages.push(message);
|
|
833
|
+
provenance.push(historyItemProvenance(replacement.item));
|
|
834
|
+
seqs.push(replacement.sourceEventSeq ?? event.seq);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (event.data.type === "user_message" || event.data.type === "assistant_message" || event.data.type === "tool_result") {
|
|
840
|
+
for (const message of durableMessages(
|
|
841
|
+
piMessageFromHistoryItem(event.data)
|
|
842
|
+
)) {
|
|
843
|
+
messages.push(message);
|
|
844
|
+
provenance.push(historyItemProvenance(event.data));
|
|
845
|
+
seqs.push(event.seq);
|
|
846
|
+
}
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (event.data.type === "authorization_completed") {
|
|
850
|
+
messages.push(
|
|
851
|
+
authorizationObservationMessage(event.data, event.createdAtMs)
|
|
852
|
+
);
|
|
853
|
+
provenance.push(contextProvenance);
|
|
854
|
+
seqs.push(event.seq);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return { messages, provenance, seqs, modelProfile, modelId };
|
|
940
858
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
859
|
+
|
|
860
|
+
// src/chat/conversations/projection.ts
|
|
861
|
+
function connectedMcpProvidersFromEvents(events) {
|
|
862
|
+
const providers = /* @__PURE__ */ new Set();
|
|
863
|
+
for (const event of events) {
|
|
864
|
+
if (event.data.type === "mcp_provider_connected") {
|
|
865
|
+
providers.add(event.data.provider);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
869
|
+
}
|
|
870
|
+
function isUserMessage(message) {
|
|
871
|
+
return message.role === "user";
|
|
872
|
+
}
|
|
873
|
+
function countMatchingPrefix(left, right) {
|
|
874
|
+
const limit = Math.min(left.length, right.length);
|
|
875
|
+
for (let index = 0; index < limit; index += 1) {
|
|
876
|
+
if (!isDeepStrictEqual3(left[index], right[index])) {
|
|
877
|
+
return index;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return limit;
|
|
881
|
+
}
|
|
882
|
+
function normalizeDurableMessage(message) {
|
|
883
|
+
return piMessageSchema.parse(
|
|
884
|
+
JSON.parse(JSON.stringify(sanitizePostgresJson(message)))
|
|
954
885
|
);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
args.
|
|
886
|
+
}
|
|
887
|
+
function resolveCommitProvenance(args) {
|
|
888
|
+
if (args.explicitProvenance) {
|
|
889
|
+
if (args.explicitProvenance.length !== args.nextMessages.length) {
|
|
890
|
+
throw new Error("commit provenance must align one-to-one with messages");
|
|
891
|
+
}
|
|
892
|
+
return args.explicitProvenance;
|
|
893
|
+
}
|
|
894
|
+
if (args.existing.provenance.length !== args.existing.messages.length) {
|
|
895
|
+
throw new Error("committed provenance must align one-to-one with messages");
|
|
896
|
+
}
|
|
897
|
+
const matchingPrefix = args.matchingPrefix;
|
|
898
|
+
const provenance = args.nextMessages.map(
|
|
899
|
+
(_, index) => index < matchingPrefix ? args.existing.provenance[index] : contextProvenance
|
|
900
|
+
);
|
|
901
|
+
if (args.newMessageProvenance) {
|
|
902
|
+
for (let index = args.nextMessages.length - 1; index >= matchingPrefix; index -= 1) {
|
|
903
|
+
if (isUserMessage(args.nextMessages[index])) {
|
|
904
|
+
provenance[index] = args.newMessageProvenance;
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (args.trailingMessageProvenance) {
|
|
910
|
+
if (args.trailingMessageProvenance.length > provenance.length) {
|
|
911
|
+
throw new Error(
|
|
912
|
+
"trailing commit provenance cannot exceed committed messages"
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
const newMessageCount = args.nextMessages.length - matchingPrefix;
|
|
916
|
+
if (args.trailingMessageProvenance.length > newMessageCount) {
|
|
917
|
+
throw new Error(
|
|
918
|
+
"trailing commit provenance must align to newly committed messages"
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
const start = provenance.length - args.trailingMessageProvenance.length;
|
|
922
|
+
args.trailingMessageProvenance.forEach((entry, offset) => {
|
|
923
|
+
provenance[start + offset] = entry;
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
return provenance;
|
|
927
|
+
}
|
|
928
|
+
async function loadProjection(args) {
|
|
929
|
+
const events = await getConversationEventStore().loadCurrentHistory(
|
|
930
|
+
args.conversationId
|
|
972
931
|
);
|
|
932
|
+
return projectConversationEvents(events).messages;
|
|
973
933
|
}
|
|
974
|
-
async function
|
|
975
|
-
const
|
|
976
|
-
args.
|
|
977
|
-
args.existing.sessionId
|
|
934
|
+
async function loadConversationProjection(args) {
|
|
935
|
+
const events = await getConversationEventStore().loadCurrentHistory(
|
|
936
|
+
args.conversationId
|
|
978
937
|
);
|
|
979
|
-
|
|
938
|
+
return projectConversationEvents(events);
|
|
939
|
+
}
|
|
940
|
+
async function openConversationProjection(args) {
|
|
941
|
+
const events = await getConversationEventStore().loadCurrentHistory(
|
|
942
|
+
args.conversationId
|
|
943
|
+
);
|
|
944
|
+
return projectConversationEvents(events);
|
|
945
|
+
}
|
|
946
|
+
async function loadTurnProjection(args) {
|
|
947
|
+
const eventStore = getConversationEventStore();
|
|
948
|
+
if (args.committedSeq < 0) {
|
|
949
|
+
return projectConversationEvents(
|
|
950
|
+
await eventStore.loadCurrentHistory(args.conversationId)
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
const historyEvents = await eventStore.loadHistoryContaining(
|
|
954
|
+
args.conversationId,
|
|
955
|
+
args.committedSeq,
|
|
956
|
+
args.includeTail ? void 0 : args.committedSeq
|
|
957
|
+
);
|
|
958
|
+
if (!historyEvents) {
|
|
980
959
|
return void 0;
|
|
981
960
|
}
|
|
982
|
-
return
|
|
983
|
-
piMessages: args.existing.piMessages,
|
|
984
|
-
piMessageProvenance: args.existing.piMessageProvenance,
|
|
985
|
-
ttlMs: AGENT_TURN_SESSION_TTL_MS,
|
|
986
|
-
...args.existing.turnStartMessageIndex !== void 0 ? { turnStartMessageIndex: args.existing.turnStartMessageIndex } : {},
|
|
987
|
-
record: buildStoredRecord({
|
|
988
|
-
conversationId: args.existing.conversationId,
|
|
989
|
-
sessionId: args.existing.sessionId,
|
|
990
|
-
sliceId: args.existing.sliceId,
|
|
991
|
-
state: args.state,
|
|
992
|
-
committedSeq: parsed.committedSeq,
|
|
993
|
-
...parsed.historyVersion !== void 0 ? { historyVersion: parsed.historyVersion } : {},
|
|
994
|
-
...parsed.turnStartSeq !== void 0 ? { turnStartSeq: parsed.turnStartSeq } : {},
|
|
995
|
-
...parsed.runtimeContext ? { runtimeContext: parsed.runtimeContext } : {},
|
|
996
|
-
...parsed.channelName ? { channelName: parsed.channelName } : {},
|
|
997
|
-
startedAtMs: parsed.startedAtMs,
|
|
998
|
-
lastProgressAtMs: parsed.lastProgressAtMs,
|
|
999
|
-
previousVersion: parsed.version,
|
|
1000
|
-
cumulativeDurationMs: args.existing.cumulativeDurationMs,
|
|
1001
|
-
...args.existing.cumulativeUsage ? { cumulativeUsage: args.existing.cumulativeUsage } : {},
|
|
1002
|
-
...args.existing.destination ? { destination: args.existing.destination } : {},
|
|
1003
|
-
...args.existing.dispatchId ? { dispatchId: args.existing.dispatchId } : {},
|
|
1004
|
-
...args.existing.dispatchOutcome ? { dispatchOutcome: args.existing.dispatchOutcome } : {},
|
|
1005
|
-
...args.existing.resultMessageId ? { resultMessageId: args.existing.resultMessageId } : {},
|
|
1006
|
-
...args.existing.source ? { source: args.existing.source } : {},
|
|
1007
|
-
...args.existing.loadedSkillNames ? { loadedSkillNames: args.existing.loadedSkillNames } : {},
|
|
1008
|
-
...args.existing.modelId ? { modelId: args.existing.modelId } : {},
|
|
1009
|
-
...args.existing.reasoningLevel ? { reasoningLevel: args.existing.reasoningLevel } : {},
|
|
1010
|
-
...args.existing.actor ? { actor: args.existing.actor } : {},
|
|
1011
|
-
actors: args.existing.actors,
|
|
1012
|
-
...args.existing.resumeReason ? { resumeReason: args.existing.resumeReason } : {},
|
|
1013
|
-
...args.existing.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.existing.resumedFromSliceId } : {},
|
|
1014
|
-
...args.existing.surface ? { surface: args.existing.surface } : {},
|
|
1015
|
-
...args.existing.traceId ? { traceId: args.existing.traceId } : {},
|
|
1016
|
-
...args.errorMessage ?? args.existing.errorMessage ? { errorMessage: args.errorMessage ?? args.existing.errorMessage } : {}
|
|
1017
|
-
})
|
|
1018
|
-
});
|
|
961
|
+
return projectConversationEvents(historyEvents);
|
|
1019
962
|
}
|
|
1020
|
-
async function
|
|
1021
|
-
const
|
|
963
|
+
async function loadConnectedMcpProviders(args) {
|
|
964
|
+
const events = await getConversationEventStore().loadCurrentHistory(
|
|
965
|
+
args.conversationId
|
|
966
|
+
);
|
|
967
|
+
return connectedMcpProvidersFromEvents(events);
|
|
968
|
+
}
|
|
969
|
+
function messageTimestamp(message) {
|
|
970
|
+
const timestamp = message.timestamp;
|
|
971
|
+
return typeof timestamp === "number" ? timestamp : Date.now();
|
|
972
|
+
}
|
|
973
|
+
async function commitMessages(args) {
|
|
974
|
+
const executor = args.executor ?? getSqlExecutor();
|
|
975
|
+
return await withConversationEventLock(
|
|
976
|
+
executor,
|
|
1022
977
|
args.conversationId,
|
|
1023
|
-
|
|
978
|
+
async () => executor.transaction(
|
|
979
|
+
async () => await commitMessagesLocked(args, executor)
|
|
980
|
+
)
|
|
1024
981
|
);
|
|
1025
|
-
const existingDispatchId = existingRecord?.dispatchId ?? (await listAgentTurnSessionSummariesForConversation(args.conversationId)).find((summary) => summary.sessionId === args.sessionId)?.dispatchId;
|
|
1026
|
-
if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
|
|
1027
|
-
throw new Error(
|
|
1028
|
-
`Turn session ${args.sessionId} dispatchId cannot be changed`
|
|
1029
|
-
);
|
|
1030
|
-
}
|
|
1031
|
-
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
1032
|
-
const instructionActor = args.actor ?? existingRecord?.actor;
|
|
1033
|
-
const commit = await commitMessages({
|
|
1034
|
-
conversationId: args.conversationId,
|
|
1035
|
-
messages: args.piMessages,
|
|
1036
|
-
...instructionActor ? { newMessageProvenance: instructionProvenanceFor(instructionActor) } : {},
|
|
1037
|
-
...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {},
|
|
1038
|
-
...args.turnContexts && args.turnContexts.length > 0 ? {
|
|
1039
|
-
turnContext: {
|
|
1040
|
-
contexts: args.turnContexts,
|
|
1041
|
-
turnId: args.sessionId
|
|
1042
|
-
}
|
|
1043
|
-
} : {}
|
|
1044
|
-
});
|
|
1045
|
-
const durableTurnStartMessageIndex = args.turnStartMessageIndex === void 0 ? void 0 : stripRuntimeTurnContext(
|
|
1046
|
-
args.piMessages.slice(0, args.turnStartMessageIndex)
|
|
1047
|
-
).length;
|
|
1048
|
-
const runtimeContext = retainRuntimeTurnContext(args.piMessages);
|
|
1049
|
-
const retainedRuntimeContext = runtimeContext.length > 0 ? runtimeContext : existingRecord?.historyVersion === commit.historyVersion ? existingRecord.runtimeContext : void 0;
|
|
1050
|
-
const turnStartSeq = durableTurnStartMessageIndex === void 0 ? existingRecord?.turnStartSeq : durableTurnStartMessageIndex <= 0 ? -1 : commit.messageSeqs[durableTurnStartMessageIndex - 1] ?? commit.committedSeq;
|
|
1051
|
-
const turnStartMessageIndex = durableTurnStartMessageIndex ?? (turnStartSeq === void 0 ? void 0 : commit.messageSeqs.filter((seq) => seq <= turnStartSeq).length);
|
|
1052
|
-
return await setStoredRecord({
|
|
1053
|
-
conversationStore: args.conversationStore,
|
|
1054
|
-
destinationVisibility: args.destinationVisibility,
|
|
1055
|
-
piMessages: commit.messages,
|
|
1056
|
-
piMessageProvenance: commit.provenance,
|
|
1057
|
-
ttlMs,
|
|
1058
|
-
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {},
|
|
1059
|
-
record: buildStoredRecord({
|
|
1060
|
-
...args.channelName ?? existingRecord?.channelName ? { channelName: args.channelName ?? existingRecord?.channelName } : {},
|
|
1061
|
-
conversationId: args.conversationId,
|
|
1062
|
-
sessionId: args.sessionId,
|
|
1063
|
-
sliceId: args.sliceId,
|
|
1064
|
-
state: args.state,
|
|
1065
|
-
...existingRecord?.startedAtMs !== void 0 ? { startedAtMs: existingRecord.startedAtMs } : {},
|
|
1066
|
-
...args.lastProgressAtMs !== void 0 ? { lastProgressAtMs: args.lastProgressAtMs } : {},
|
|
1067
|
-
committedSeq: commit.committedSeq,
|
|
1068
|
-
historyVersion: commit.historyVersion,
|
|
1069
|
-
...turnStartSeq !== void 0 ? { turnStartSeq } : {},
|
|
1070
|
-
...retainedRuntimeContext ? { runtimeContext: retainedRuntimeContext } : {},
|
|
1071
|
-
previousVersion: existingRecord?.version,
|
|
1072
|
-
cumulativeDurationMs: args.cumulativeDurationMs ?? existingRecord?.cumulativeDurationMs ?? 0,
|
|
1073
|
-
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
1074
|
-
...args.destination ?? existingRecord?.destination ? { destination: args.destination ?? existingRecord?.destination } : {},
|
|
1075
|
-
...args.dispatchId ?? existingRecord?.dispatchId ? { dispatchId: args.dispatchId ?? existingRecord?.dispatchId } : {},
|
|
1076
|
-
...args.dispatchOutcome ?? existingRecord?.dispatchOutcome ? {
|
|
1077
|
-
dispatchOutcome: args.dispatchOutcome ?? existingRecord?.dispatchOutcome
|
|
1078
|
-
} : {},
|
|
1079
|
-
...args.resultMessageId ?? existingRecord?.resultMessageId ? {
|
|
1080
|
-
resultMessageId: args.resultMessageId ?? existingRecord?.resultMessageId
|
|
1081
|
-
} : {},
|
|
1082
|
-
...args.source ?? existingRecord?.source ? { source: args.source ?? existingRecord?.source } : {},
|
|
1083
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1084
|
-
modelId: args.modelId,
|
|
1085
|
-
...args.reasoningLevel ?? existingRecord?.reasoningLevel ? {
|
|
1086
|
-
reasoningLevel: args.reasoningLevel ?? existingRecord?.reasoningLevel
|
|
1087
|
-
} : {},
|
|
1088
|
-
...args.actor ?? existingRecord?.actor ? { actor: args.actor ?? existingRecord?.actor } : {},
|
|
1089
|
-
actors: instructionActors([
|
|
1090
|
-
...(existingRecord?.actors ?? []).map(instructionProvenanceFor),
|
|
1091
|
-
...(args.actors ?? []).map(instructionProvenanceFor),
|
|
1092
|
-
...commit.provenance
|
|
1093
|
-
]),
|
|
1094
|
-
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
1095
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
1096
|
-
...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
|
|
1097
|
-
...args.surface ?? existingRecord?.surface ? { surface: args.surface ?? existingRecord?.surface } : {},
|
|
1098
|
-
...args.traceId ?? existingRecord?.traceId ? { traceId: args.traceId ?? existingRecord?.traceId } : {}
|
|
1099
|
-
})
|
|
1100
|
-
});
|
|
1101
982
|
}
|
|
1102
|
-
async function
|
|
1103
|
-
const
|
|
983
|
+
async function commitAcceptedReply(args) {
|
|
984
|
+
const executor = getSqlExecutor();
|
|
985
|
+
await withConversationEventLock(
|
|
986
|
+
executor,
|
|
1104
987
|
args.conversationId,
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const summary = {
|
|
1120
|
-
version: existing?.version ?? 0,
|
|
1121
|
-
...args.channelName ?? existing?.channelName ? { channelName: args.channelName ?? existing?.channelName } : {},
|
|
1122
|
-
conversationId: args.conversationId,
|
|
1123
|
-
sessionId: args.sessionId,
|
|
1124
|
-
sliceId: args.sliceId,
|
|
1125
|
-
startedAtMs: existing?.startedAtMs ?? args.startedAtMs ?? nowMs,
|
|
1126
|
-
lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
|
|
1127
|
-
state: args.state,
|
|
1128
|
-
updatedAtMs: nowMs,
|
|
1129
|
-
cumulativeDurationMs: args.cumulativeDurationMs ?? existing?.cumulativeDurationMs ?? 0,
|
|
1130
|
-
...args.cumulativeUsage ?? existing?.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage ?? existing?.cumulativeUsage } : {},
|
|
1131
|
-
...args.destination ?? existing?.destination ? { destination: args.destination ?? existing?.destination } : {},
|
|
1132
|
-
...args.dispatchId ?? existingDispatchId ? { dispatchId: args.dispatchId ?? existingDispatchId } : {},
|
|
1133
|
-
...args.dispatchOutcome ?? existingDispatchOutcome ? { dispatchOutcome: args.dispatchOutcome ?? existingDispatchOutcome } : {},
|
|
1134
|
-
...args.resultMessageId ?? existingResultMessageId ? { resultMessageId: args.resultMessageId ?? existingResultMessageId } : {},
|
|
1135
|
-
...args.source ?? existing?.source ? { source: args.source ?? existing?.source } : {},
|
|
1136
|
-
...args.actor ?? existing?.actor ? { actor: args.actor ?? existing?.actor } : {},
|
|
1137
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : existing?.loadedSkillNames ? { loadedSkillNames: existing.loadedSkillNames } : {},
|
|
1138
|
-
...args.modelId ?? existing?.modelId ? { modelId: args.modelId ?? existing?.modelId } : {},
|
|
1139
|
-
...args.reasoningLevel ?? existing?.reasoningLevel ? { reasoningLevel: args.reasoningLevel ?? existing?.reasoningLevel } : {},
|
|
1140
|
-
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
1141
|
-
...args.surface ?? existing?.surface ? { surface: args.surface ?? existing?.surface } : {},
|
|
1142
|
-
...args.traceId ?? existing?.traceId ? { traceId: args.traceId ?? existing?.traceId } : {}
|
|
1143
|
-
};
|
|
1144
|
-
await recordConversationActivityMetadata({
|
|
1145
|
-
conversationStore: args.conversationStore,
|
|
1146
|
-
destinationVisibility: args.destinationVisibility,
|
|
1147
|
-
nowMs,
|
|
1148
|
-
summary
|
|
1149
|
-
});
|
|
1150
|
-
await appendAgentTurnSessionSummary(summary, ttlMs);
|
|
1151
|
-
}
|
|
1152
|
-
async function readAgentTurnSessionSummariesFromIndex(key, stateAdapter) {
|
|
1153
|
-
await stateAdapter.connect();
|
|
1154
|
-
const values = await stateAdapter.getList(key);
|
|
1155
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
1156
|
-
for (const value of [...values].reverse()) {
|
|
1157
|
-
let summary;
|
|
1158
|
-
try {
|
|
1159
|
-
summary = parseAgentTurnSessionSummary(value);
|
|
1160
|
-
} catch (error) {
|
|
1161
|
-
logWarn(
|
|
1162
|
-
"agent_turn_session_summary_parse_failed",
|
|
1163
|
-
{},
|
|
988
|
+
async () => executor.transaction(async () => {
|
|
989
|
+
const agentMessage = normalizeDurableMessage(args.agentMessage);
|
|
990
|
+
await createSqlConversationEventStore(executor).append(
|
|
991
|
+
args.conversationId,
|
|
992
|
+
[
|
|
993
|
+
{
|
|
994
|
+
idempotencyKey: `message:${args.conversationMessageId}:agent`,
|
|
995
|
+
data: historyItemFromPiMessage(agentMessage, contextProvenance),
|
|
996
|
+
createdAtMs: messageTimestamp(agentMessage)
|
|
997
|
+
}
|
|
998
|
+
]
|
|
999
|
+
);
|
|
1000
|
+
await appendConversationMessages(
|
|
1001
|
+
createSqlConversationEventStore(executor),
|
|
1164
1002
|
{
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1003
|
+
conversation: args.conversation,
|
|
1004
|
+
conversationId: args.conversationId,
|
|
1005
|
+
...args.repliedAtMs === void 0 ? {} : { repliedAtMs: args.repliedAtMs }
|
|
1006
|
+
}
|
|
1169
1007
|
);
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
const summaryKey = `${summary.conversationId}:${summary.sessionId}`;
|
|
1173
|
-
if (!summaries.has(summaryKey)) {
|
|
1174
|
-
summaries.set(summaryKey, summary);
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
return [...summaries.values()].sort(
|
|
1178
|
-
(left, right) => right.updatedAtMs - left.updatedAtMs
|
|
1008
|
+
})
|
|
1179
1009
|
);
|
|
1180
1010
|
}
|
|
1181
|
-
async function
|
|
1182
|
-
const
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
return summaries;
|
|
1186
|
-
}
|
|
1187
|
-
return (await readAgentTurnSessionSummariesFromIndex(
|
|
1188
|
-
AGENT_TURN_SESSION_INDEX_KEY,
|
|
1189
|
-
stateAdapter
|
|
1190
|
-
)).filter((summary) => summary.conversationId === conversationId);
|
|
1191
|
-
}
|
|
1192
|
-
async function listBoundedAgentTurnSessionSummariesForConversation(conversationId) {
|
|
1193
|
-
return readAgentTurnSessionSummariesFromIndex(
|
|
1194
|
-
agentTurnSessionConversationIndexKey(conversationId),
|
|
1195
|
-
getStateAdapter()
|
|
1011
|
+
async function commitMessagesLocked(args, executor) {
|
|
1012
|
+
const eventStore = createSqlConversationEventStore(executor);
|
|
1013
|
+
const currentEvents = await eventStore.loadCurrentHistory(
|
|
1014
|
+
args.conversationId
|
|
1196
1015
|
);
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
args.conversationId,
|
|
1201
|
-
args.sessionId
|
|
1016
|
+
const current = projectConversationEvents(currentEvents);
|
|
1017
|
+
const nextLocalMessages = stripRuntimeTurnContext(args.messages).map(
|
|
1018
|
+
normalizeDurableMessage
|
|
1202
1019
|
);
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
return await updateAgentTurnSessionState({
|
|
1207
|
-
existing,
|
|
1208
|
-
state: "abandoned",
|
|
1209
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
async function failAgentTurnSessionRecord(args) {
|
|
1213
|
-
const existing = await getAgentTurnSessionRecord(
|
|
1214
|
-
args.conversationId,
|
|
1215
|
-
args.sessionId
|
|
1020
|
+
const matchingPrefix = countMatchingPrefix(
|
|
1021
|
+
current.messages,
|
|
1022
|
+
nextLocalMessages
|
|
1216
1023
|
);
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1024
|
+
const nextLocalProvenance = resolveCommitProvenance({
|
|
1025
|
+
existing: current,
|
|
1026
|
+
nextMessages: nextLocalMessages,
|
|
1027
|
+
matchingPrefix,
|
|
1028
|
+
...args.provenance ? { explicitProvenance: args.provenance } : {},
|
|
1029
|
+
...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {},
|
|
1030
|
+
...args.newMessageProvenance ? { newMessageProvenance: args.newMessageProvenance } : {}
|
|
1224
1031
|
});
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1032
|
+
if (matchingPrefix === current.messages.length) {
|
|
1033
|
+
const newMessages = nextLocalMessages.slice(matchingPrefix);
|
|
1034
|
+
const turnContext = args.turnContext;
|
|
1035
|
+
const turnContextEvents = turnContext?.contexts.map((context, index) => ({
|
|
1036
|
+
idempotencyKey: `turn:${turnContext.turnId}:context:${context.pluginName}:${index}`,
|
|
1037
|
+
createdAtMs: context.loadedAtMs,
|
|
1038
|
+
data: {
|
|
1039
|
+
type: "turn_context",
|
|
1040
|
+
turnId: turnContext.turnId,
|
|
1041
|
+
pluginName: context.pluginName,
|
|
1042
|
+
kind: context.kind,
|
|
1043
|
+
version: context.version,
|
|
1044
|
+
content: context.content
|
|
1045
|
+
}
|
|
1046
|
+
})) ?? [];
|
|
1047
|
+
await eventStore.append(args.conversationId, [
|
|
1048
|
+
...newMessages.map((message, index) => ({
|
|
1049
|
+
data: historyItemFromPiMessage(
|
|
1050
|
+
message,
|
|
1051
|
+
nextLocalProvenance[matchingPrefix + index]
|
|
1052
|
+
),
|
|
1053
|
+
createdAtMs: messageTimestamp(message)
|
|
1054
|
+
})),
|
|
1055
|
+
...turnContextEvents
|
|
1056
|
+
]);
|
|
1057
|
+
} else {
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
`Agent history for ${args.conversationId} changed before its committed boundary`
|
|
1060
|
+
);
|
|
1232
1061
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
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}\`.`;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// src/chat/services/turn-session-record.ts
|
|
1239
|
-
function logSessionRecordError(error, eventName, args, attributes, message) {
|
|
1240
|
-
logException(
|
|
1241
|
-
error,
|
|
1242
|
-
eventName,
|
|
1243
|
-
{
|
|
1244
|
-
messageConversationId: args.logContext.threadId,
|
|
1245
|
-
userId: args.logContext.actorId,
|
|
1246
|
-
destinationName: args.logContext.channelId,
|
|
1247
|
-
runId: args.logContext.runId,
|
|
1248
|
-
assistantUserName: args.logContext.assistantUserName,
|
|
1249
|
-
modelId: args.modelId
|
|
1250
|
-
},
|
|
1251
|
-
{
|
|
1252
|
-
"app.ai.resume_conversation_id": args.conversationId,
|
|
1253
|
-
"app.ai.resume_session_id": args.sessionId,
|
|
1254
|
-
...attributes
|
|
1255
|
-
},
|
|
1256
|
-
message
|
|
1062
|
+
const committedEvents = await eventStore.loadCurrentHistory(
|
|
1063
|
+
args.conversationId
|
|
1257
1064
|
);
|
|
1065
|
+
const committed = projectConversationEvents(committedEvents);
|
|
1066
|
+
return {
|
|
1067
|
+
committedSeq: committedEvents.at(-1)?.seq ?? -1,
|
|
1068
|
+
historyVersion: committedEvents.at(-1)?.historyVersion ?? 0,
|
|
1069
|
+
messageSeqs: committed.seqs,
|
|
1070
|
+
messages: nextLocalMessages,
|
|
1071
|
+
provenance: nextLocalProvenance
|
|
1072
|
+
};
|
|
1258
1073
|
}
|
|
1259
|
-
function
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1074
|
+
async function recordMcpProviderConnected(args) {
|
|
1075
|
+
const eventStore = getConversationEventStore();
|
|
1076
|
+
const events = await eventStore.loadCurrentHistory(args.conversationId);
|
|
1077
|
+
if (connectedMcpProvidersFromEvents(events).includes(args.provider)) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
await eventStore.append(args.conversationId, [
|
|
1081
|
+
{
|
|
1082
|
+
data: { type: "mcp_provider_connected", provider: args.provider },
|
|
1083
|
+
createdAtMs: Date.now()
|
|
1084
|
+
}
|
|
1085
|
+
]);
|
|
1267
1086
|
}
|
|
1268
|
-
function
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
|
|
1087
|
+
async function recordAuthorizationRequested(args) {
|
|
1088
|
+
const eventStore = getConversationEventStore();
|
|
1089
|
+
const events = await eventStore.loadCurrentHistory(args.conversationId);
|
|
1090
|
+
if (events.some(
|
|
1091
|
+
(event) => event.data.type === "authorization_requested" && event.data.authorizationId === args.authorizationId
|
|
1092
|
+
)) {
|
|
1093
|
+
return;
|
|
1272
1094
|
}
|
|
1273
|
-
|
|
1095
|
+
await eventStore.append(args.conversationId, [
|
|
1096
|
+
{
|
|
1097
|
+
data: {
|
|
1098
|
+
type: "authorization_requested",
|
|
1099
|
+
kind: args.kind,
|
|
1100
|
+
provider: args.provider,
|
|
1101
|
+
actorId: args.actorId,
|
|
1102
|
+
authorizationId: args.authorizationId,
|
|
1103
|
+
delivery: args.delivery
|
|
1104
|
+
},
|
|
1105
|
+
createdAtMs: Date.now()
|
|
1106
|
+
}
|
|
1107
|
+
]);
|
|
1274
1108
|
}
|
|
1275
|
-
async function
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1109
|
+
async function recordAuthorizationCompleted(args) {
|
|
1110
|
+
const eventStore = getConversationEventStore();
|
|
1111
|
+
const events = await eventStore.loadCurrentHistory(args.conversationId);
|
|
1112
|
+
if (events.some(
|
|
1113
|
+
(event) => event.data.type === "authorization_completed" && event.data.authorizationId === args.authorizationId
|
|
1114
|
+
)) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
await eventStore.append(args.conversationId, [
|
|
1118
|
+
{
|
|
1119
|
+
data: {
|
|
1120
|
+
type: "authorization_completed",
|
|
1121
|
+
kind: args.kind,
|
|
1122
|
+
provider: args.provider,
|
|
1123
|
+
actorId: args.actorId,
|
|
1124
|
+
authorizationId: args.authorizationId
|
|
1125
|
+
},
|
|
1126
|
+
createdAtMs: Date.now()
|
|
1127
|
+
}
|
|
1128
|
+
]);
|
|
1288
1129
|
}
|
|
1289
|
-
async function
|
|
1290
|
-
|
|
1291
|
-
|
|
1130
|
+
async function loadTurnRoute(args) {
|
|
1131
|
+
const event = await getConversationEventStore().loadByIdempotencyKey(
|
|
1132
|
+
args.conversationId,
|
|
1133
|
+
`turn:${args.turnId}:routed`
|
|
1134
|
+
);
|
|
1135
|
+
if (!event) {
|
|
1136
|
+
return void 0;
|
|
1292
1137
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return true;
|
|
1323
|
-
} catch (recordError) {
|
|
1324
|
-
logSessionRecordError(
|
|
1325
|
-
recordError,
|
|
1326
|
-
"agent_turn_running_session_record_failed",
|
|
1327
|
-
args,
|
|
1328
|
-
{
|
|
1329
|
-
"app.ai.resume_slice_id": args.sliceId
|
|
1138
|
+
if (event.data.type !== "turn_routed" || event.data.turnId !== args.turnId) {
|
|
1139
|
+
throw new Error(`Turn route key for "${args.turnId}" has invalid data`);
|
|
1140
|
+
}
|
|
1141
|
+
return event.data;
|
|
1142
|
+
}
|
|
1143
|
+
async function recordTurnRoute(args) {
|
|
1144
|
+
await getConversationEventStore().append(args.conversationId, [
|
|
1145
|
+
{
|
|
1146
|
+
idempotencyKey: `turn:${args.turnId}:routed`,
|
|
1147
|
+
createdAtMs: Date.now(),
|
|
1148
|
+
data: {
|
|
1149
|
+
type: "turn_routed",
|
|
1150
|
+
turnId: args.turnId,
|
|
1151
|
+
modelProfile: args.modelProfile,
|
|
1152
|
+
modelId: args.modelId,
|
|
1153
|
+
reasoningLevel: args.reasoningLevel,
|
|
1154
|
+
...args.confidence !== void 0 ? { confidence: args.confidence } : {},
|
|
1155
|
+
source: args.source
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
]);
|
|
1159
|
+
}
|
|
1160
|
+
async function recordToolExecutionStarted(args) {
|
|
1161
|
+
await getConversationEventStore().append(args.conversationId, [
|
|
1162
|
+
{
|
|
1163
|
+
data: {
|
|
1164
|
+
type: "tool_execution_started",
|
|
1165
|
+
toolCallId: args.toolCallId,
|
|
1166
|
+
toolName: args.toolName
|
|
1330
1167
|
},
|
|
1331
|
-
|
|
1168
|
+
createdAtMs: args.createdAtMs ?? Date.now()
|
|
1169
|
+
}
|
|
1170
|
+
]);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/chat/state/turn-id.ts
|
|
1174
|
+
function buildDeterministicTurnId(messageId) {
|
|
1175
|
+
const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1176
|
+
return `turn_${sanitized}`;
|
|
1177
|
+
}
|
|
1178
|
+
function buildDeterministicAssistantMessageId(turnId) {
|
|
1179
|
+
return `${turnId}:assistant:1`;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/chat/runtime/turn.ts
|
|
1183
|
+
var ConversationTurnBoundaryError = class extends Error {
|
|
1184
|
+
eventId;
|
|
1185
|
+
failureCode;
|
|
1186
|
+
constructor(args) {
|
|
1187
|
+
super(
|
|
1188
|
+
args.cause instanceof Error ? args.cause.message : String(args.cause),
|
|
1189
|
+
{
|
|
1190
|
+
cause: args.cause
|
|
1191
|
+
}
|
|
1332
1192
|
);
|
|
1333
|
-
|
|
1193
|
+
this.name = "ConversationTurnBoundaryError";
|
|
1194
|
+
this.eventId = args.eventId;
|
|
1195
|
+
this.failureCode = args.failureCode;
|
|
1334
1196
|
}
|
|
1197
|
+
};
|
|
1198
|
+
function getConversationTurnBoundaryError(error) {
|
|
1199
|
+
return error instanceof ConversationTurnBoundaryError ? error : void 0;
|
|
1335
1200
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
args.sessionId
|
|
1342
|
-
);
|
|
1343
|
-
});
|
|
1344
|
-
const sliceId = args.sliceId ?? latestSessionRecord?.sliceId;
|
|
1345
|
-
if (sliceId === void 0) {
|
|
1346
|
-
throw new Error(
|
|
1347
|
-
"Completed session record requires a slice id from the caller or the latest stored record"
|
|
1348
|
-
);
|
|
1201
|
+
var CooperativeTurnYieldError = class extends Error {
|
|
1202
|
+
code = "cooperative_turn_yield";
|
|
1203
|
+
constructor(message = "Agent turn yielded at a safe boundary") {
|
|
1204
|
+
super(message);
|
|
1205
|
+
this.name = "CooperativeTurnYieldError";
|
|
1349
1206
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1354
|
-
conversationId: args.conversationId,
|
|
1355
|
-
cumulativeDurationMs: addDurationMs(
|
|
1356
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
1357
|
-
args.currentDurationMs
|
|
1358
|
-
),
|
|
1359
|
-
cumulativeUsage: addAgentTurnUsage(
|
|
1360
|
-
latestSessionRecord?.cumulativeUsage,
|
|
1361
|
-
args.currentUsage
|
|
1362
|
-
),
|
|
1363
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1364
|
-
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
1365
|
-
...args.dispatchOutcome ?? latestSessionRecord?.dispatchOutcome ? {
|
|
1366
|
-
dispatchOutcome: args.dispatchOutcome ?? latestSessionRecord?.dispatchOutcome
|
|
1367
|
-
} : {},
|
|
1368
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
1369
|
-
...args.resultMessageId ?? latestSessionRecord?.resultMessageId ? {
|
|
1370
|
-
resultMessageId: args.resultMessageId ?? latestSessionRecord?.resultMessageId
|
|
1371
|
-
} : {},
|
|
1372
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1373
|
-
...args.destinationVisibility ? { destinationVisibility: args.destinationVisibility } : {},
|
|
1374
|
-
sessionId: args.sessionId,
|
|
1375
|
-
sliceId,
|
|
1376
|
-
state: "completed",
|
|
1377
|
-
piMessages: args.allMessages,
|
|
1378
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1379
|
-
...args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames ? {
|
|
1380
|
-
loadedSkillNames: args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames
|
|
1381
|
-
} : {},
|
|
1382
|
-
modelId,
|
|
1383
|
-
...reasoningLevel ? { reasoningLevel } : {},
|
|
1384
|
-
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
1385
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
1386
|
-
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
1387
|
-
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
1388
|
-
} : {}
|
|
1389
|
-
};
|
|
1390
|
-
await persistWithRetry(async () => {
|
|
1391
|
-
await upsertAgentTurnSessionRecord(target);
|
|
1392
|
-
});
|
|
1207
|
+
};
|
|
1208
|
+
function isCooperativeTurnYieldError(error) {
|
|
1209
|
+
return error instanceof CooperativeTurnYieldError;
|
|
1393
1210
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
dispatchOutcome: args.dispatchOutcome,
|
|
1404
|
-
errorMessage: args.errorMessage,
|
|
1405
|
-
resultMessageId: args.resultMessageId,
|
|
1406
|
-
source: args.source,
|
|
1407
|
-
sessionId: args.sessionId,
|
|
1408
|
-
sliceId: args.sliceId,
|
|
1409
|
-
allMessages: args.messages,
|
|
1410
|
-
loadedSkillNames: args.loadedSkillNames,
|
|
1411
|
-
modelId: args.modelId,
|
|
1412
|
-
actor: args.actor,
|
|
1413
|
-
reasoningLevel: args.reasoningLevel,
|
|
1414
|
-
surface: args.surface,
|
|
1415
|
-
turnStartMessageIndex: args.turnStartMessageIndex
|
|
1416
|
-
});
|
|
1211
|
+
var TurnInputCommitLostError = class extends Error {
|
|
1212
|
+
code = "turn_input_commit_lost";
|
|
1213
|
+
constructor(message = "Turn input commit lost its durable owner") {
|
|
1214
|
+
super(message);
|
|
1215
|
+
this.name = "TurnInputCommitLostError";
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
function isTurnInputCommitLostError(error) {
|
|
1219
|
+
return error instanceof TurnInputCommitLostError;
|
|
1417
1220
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
args.sessionId
|
|
1424
|
-
);
|
|
1425
|
-
const piMessages = resumableBoundary(
|
|
1426
|
-
args.messages,
|
|
1427
|
-
latestSessionRecord?.piMessages
|
|
1428
|
-
);
|
|
1429
|
-
if (piMessages.length > 0 && !isContinuablePiBoundary(piMessages)) {
|
|
1430
|
-
return void 0;
|
|
1431
|
-
}
|
|
1432
|
-
return await upsertAgentTurnSessionRecord({
|
|
1433
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1434
|
-
conversationId: args.conversationId,
|
|
1435
|
-
cumulativeDurationMs: addDurationMs(
|
|
1436
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
1437
|
-
args.currentDurationMs
|
|
1438
|
-
),
|
|
1439
|
-
cumulativeUsage: addAgentTurnUsage(
|
|
1440
|
-
latestSessionRecord?.cumulativeUsage,
|
|
1441
|
-
args.currentUsage
|
|
1442
|
-
),
|
|
1443
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1444
|
-
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
1445
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1446
|
-
sessionId: args.sessionId,
|
|
1447
|
-
sliceId: nextSliceId,
|
|
1448
|
-
state: "awaiting_resume",
|
|
1449
|
-
piMessages,
|
|
1450
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1451
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1452
|
-
modelId: args.modelId,
|
|
1453
|
-
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
1454
|
-
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
1455
|
-
} : {},
|
|
1456
|
-
resumeReason: "auth",
|
|
1457
|
-
resumedFromSliceId: args.currentSliceId,
|
|
1458
|
-
errorMessage: args.errorMessage,
|
|
1459
|
-
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
1460
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1461
|
-
});
|
|
1462
|
-
} catch (recordError) {
|
|
1463
|
-
logSessionRecordError(
|
|
1464
|
-
recordError,
|
|
1465
|
-
"agent_turn_auth_resume_session_record_failed",
|
|
1466
|
-
args,
|
|
1467
|
-
{
|
|
1468
|
-
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
1469
|
-
"app.ai.resume_next_slice_id": nextSliceId
|
|
1470
|
-
},
|
|
1471
|
-
"Failed to persist auth session record before retry"
|
|
1472
|
-
);
|
|
1221
|
+
var TurnInputDeferredError = class extends Error {
|
|
1222
|
+
code = "turn_input_deferred";
|
|
1223
|
+
constructor(message = "Turn input is deferred until the active resume ends") {
|
|
1224
|
+
super(message);
|
|
1225
|
+
this.name = "TurnInputDeferredError";
|
|
1473
1226
|
}
|
|
1474
|
-
|
|
1227
|
+
};
|
|
1228
|
+
function isTurnInputDeferredError(error) {
|
|
1229
|
+
return error instanceof TurnInputDeferredError;
|
|
1475
1230
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
const piMessages = resumableBoundary(
|
|
1484
|
-
args.messages,
|
|
1485
|
-
latestSessionRecord?.piMessages
|
|
1486
|
-
);
|
|
1487
|
-
if (piMessages.length === 0 || !isContinuablePiBoundary(piMessages)) {
|
|
1488
|
-
return void 0;
|
|
1489
|
-
}
|
|
1490
|
-
const cumulativeDurationMs = addDurationMs(
|
|
1491
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
1492
|
-
args.currentDurationMs
|
|
1493
|
-
);
|
|
1494
|
-
const cumulativeUsage = addAgentTurnUsage(
|
|
1495
|
-
latestSessionRecord?.cumulativeUsage,
|
|
1496
|
-
args.currentUsage
|
|
1497
|
-
);
|
|
1498
|
-
if (nextSliceId > botConfig.maxSlicesPerTurn) {
|
|
1499
|
-
return await upsertAgentTurnSessionRecord({
|
|
1500
|
-
...args.channelName ?? latestSessionRecord?.channelName ? {
|
|
1501
|
-
channelName: args.channelName ?? latestSessionRecord?.channelName
|
|
1502
|
-
} : {},
|
|
1503
|
-
conversationId: args.conversationId,
|
|
1504
|
-
cumulativeDurationMs,
|
|
1505
|
-
cumulativeUsage,
|
|
1506
|
-
...args.destination ?? latestSessionRecord?.destination ? {
|
|
1507
|
-
destination: args.destination ?? latestSessionRecord?.destination
|
|
1508
|
-
} : {},
|
|
1509
|
-
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
1510
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1511
|
-
sessionId: args.sessionId,
|
|
1512
|
-
sliceId: args.currentSliceId,
|
|
1513
|
-
state: "failed",
|
|
1514
|
-
piMessages,
|
|
1515
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1516
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1517
|
-
modelId: args.modelId,
|
|
1518
|
-
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
1519
|
-
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
1520
|
-
} : {},
|
|
1521
|
-
resumeReason: args.resumeReason,
|
|
1522
|
-
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
1523
|
-
errorMessage: new TurnSliceLimitExceededError(
|
|
1524
|
-
botConfig.maxSlicesPerTurn
|
|
1525
|
-
).message,
|
|
1526
|
-
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
1527
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
return await upsertAgentTurnSessionRecord({
|
|
1531
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1532
|
-
conversationId: args.conversationId,
|
|
1533
|
-
cumulativeDurationMs,
|
|
1534
|
-
cumulativeUsage,
|
|
1535
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1536
|
-
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
1537
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1538
|
-
sessionId: args.sessionId,
|
|
1539
|
-
sliceId: nextSliceId,
|
|
1540
|
-
state: "awaiting_resume",
|
|
1541
|
-
piMessages,
|
|
1542
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1543
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1544
|
-
modelId: args.modelId,
|
|
1545
|
-
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
1546
|
-
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
1547
|
-
} : {},
|
|
1548
|
-
resumeReason: args.resumeReason,
|
|
1549
|
-
resumedFromSliceId: args.currentSliceId,
|
|
1550
|
-
errorMessage: args.errorMessage,
|
|
1551
|
-
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
1552
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1553
|
-
});
|
|
1554
|
-
} catch (recordError) {
|
|
1555
|
-
logSessionRecordError(
|
|
1556
|
-
recordError,
|
|
1557
|
-
"agent_continue_session_record_failed",
|
|
1558
|
-
args,
|
|
1559
|
-
{
|
|
1560
|
-
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
1561
|
-
"app.ai.resume_next_slice_id": nextSliceId
|
|
1562
|
-
},
|
|
1563
|
-
"Failed to persist session record before scheduling agent continuation"
|
|
1564
|
-
);
|
|
1565
|
-
return void 0;
|
|
1231
|
+
function startActiveTurn(args) {
|
|
1232
|
+
args.conversation.processing.activeTurnId = args.nextTurnId;
|
|
1233
|
+
args.updateConversationStats(args.conversation);
|
|
1234
|
+
}
|
|
1235
|
+
function clearActiveTurn(conversation, sessionId) {
|
|
1236
|
+
if (!sessionId || conversation.processing.activeTurnId === sessionId) {
|
|
1237
|
+
conversation.processing.activeTurnId = void 0;
|
|
1566
1238
|
}
|
|
1567
1239
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1240
|
+
function markTurnClosed(args) {
|
|
1241
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
1242
|
+
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
1243
|
+
args.updateConversationStats(args.conversation);
|
|
1244
|
+
}
|
|
1245
|
+
function markTurnCompleted(args) {
|
|
1246
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
1247
|
+
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
1248
|
+
args.updateConversationStats(args.conversation);
|
|
1249
|
+
}
|
|
1250
|
+
function markTurnFailed(args) {
|
|
1251
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
1252
|
+
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
1253
|
+
args.markConversationMessage(args.conversation, args.userMessageId, {
|
|
1254
|
+
replied: false,
|
|
1255
|
+
skippedReason: "reply failed"
|
|
1256
|
+
});
|
|
1257
|
+
args.updateConversationStats(args.conversation);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/chat/no-reply.ts
|
|
1261
|
+
var NO_REPLY_MARKER = "[[NO_REPLY]]";
|
|
1262
|
+
function isNoReplyMarker(text) {
|
|
1263
|
+
return text.trim() === NO_REPLY_MARKER;
|
|
1264
|
+
}
|
|
1265
|
+
function containsNoReplyMarker(text) {
|
|
1266
|
+
return text.includes(NO_REPLY_MARKER);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/chat/services/assistant-reply.ts
|
|
1270
|
+
var THINKING_XML_BLOCK_PATTERN = /[ \t]*<thinking\b[^>]*>[\s\S]*?<\/thinking>[ \t]*(?:\r?\n)?/gi;
|
|
1271
|
+
var FENCED_CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
|
|
1272
|
+
function sanitizeAssistantText(text) {
|
|
1273
|
+
let result = "";
|
|
1274
|
+
let cursor = 0;
|
|
1275
|
+
for (const match of text.matchAll(FENCED_CODE_BLOCK_PATTERN)) {
|
|
1276
|
+
const start = match.index;
|
|
1277
|
+
if (start === void 0) continue;
|
|
1278
|
+
result += text.slice(cursor, start).replace(THINKING_XML_BLOCK_PATTERN, "");
|
|
1279
|
+
result += match[0];
|
|
1280
|
+
cursor = start + match[0].length;
|
|
1281
|
+
}
|
|
1282
|
+
return (result + text.slice(cursor).replace(THINKING_XML_BLOCK_PATTERN, "")).trim();
|
|
1283
|
+
}
|
|
1284
|
+
function decideReply(message) {
|
|
1285
|
+
if (message.content.some((part) => part.type === "toolCall")) {
|
|
1286
|
+
return { kind: "suppress" };
|
|
1287
|
+
}
|
|
1288
|
+
const text = sanitizeAssistantText(extractAssistantText(message));
|
|
1289
|
+
if (!text) return { kind: "empty" };
|
|
1290
|
+
if (isNoReplyMarker(text) || containsNoReplyMarker(text)) {
|
|
1291
|
+
return { kind: "suppress" };
|
|
1292
|
+
}
|
|
1293
|
+
return { kind: "deliver", text };
|
|
1294
|
+
}
|
|
1295
|
+
function getAssistantReplyText(message) {
|
|
1296
|
+
const decision = decideReply(message);
|
|
1297
|
+
return decision.kind === "deliver" ? decision.text : void 0;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/chat/services/persist-retry.ts
|
|
1301
|
+
var PERSIST_ATTEMPTS = 3;
|
|
1302
|
+
async function persistWithRetry(persist) {
|
|
1303
|
+
let lastError;
|
|
1304
|
+
for (let attempt = 1; attempt <= PERSIST_ATTEMPTS; attempt += 1) {
|
|
1305
|
+
try {
|
|
1306
|
+
await persist();
|
|
1307
|
+
return;
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
lastError = error;
|
|
1310
|
+
if (attempt < PERSIST_ATTEMPTS) {
|
|
1311
|
+
await sleep(attempt * 100);
|
|
1312
|
+
}
|
|
1573
1313
|
}
|
|
1574
|
-
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
1575
|
-
args.conversationId,
|
|
1576
|
-
args.sessionId
|
|
1577
|
-
);
|
|
1578
|
-
return await upsertAgentTurnSessionRecord({
|
|
1579
|
-
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1580
|
-
conversationId: args.conversationId,
|
|
1581
|
-
cumulativeDurationMs: addDurationMs(
|
|
1582
|
-
latestSessionRecord?.cumulativeDurationMs,
|
|
1583
|
-
args.currentDurationMs
|
|
1584
|
-
),
|
|
1585
|
-
cumulativeUsage: addAgentTurnUsage(
|
|
1586
|
-
latestSessionRecord?.cumulativeUsage,
|
|
1587
|
-
args.currentUsage
|
|
1588
|
-
),
|
|
1589
|
-
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1590
|
-
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
1591
|
-
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1592
|
-
sessionId: args.sessionId,
|
|
1593
|
-
sliceId: args.currentSliceId,
|
|
1594
|
-
state: "awaiting_resume",
|
|
1595
|
-
piMessages,
|
|
1596
|
-
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1597
|
-
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1598
|
-
modelId: args.modelId,
|
|
1599
|
-
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
1600
|
-
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
1601
|
-
} : {},
|
|
1602
|
-
resumeReason: "yield",
|
|
1603
|
-
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
1604
|
-
errorMessage: args.errorMessage,
|
|
1605
|
-
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
1606
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
1607
|
-
});
|
|
1608
|
-
} catch (recordError) {
|
|
1609
|
-
logSessionRecordError(
|
|
1610
|
-
recordError,
|
|
1611
|
-
"agent_turn_yield_session_record_failed",
|
|
1612
|
-
args,
|
|
1613
|
-
{
|
|
1614
|
-
"app.ai.resume_slice_id": args.currentSliceId
|
|
1615
|
-
},
|
|
1616
|
-
"Failed to persist cooperative yield session record"
|
|
1617
|
-
);
|
|
1618
|
-
return void 0;
|
|
1619
1314
|
}
|
|
1315
|
+
throw lastError;
|
|
1620
1316
|
}
|
|
1621
1317
|
|
|
1622
|
-
// src/chat/state/
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1318
|
+
// src/chat/state/turn-session.ts
|
|
1319
|
+
import { THREAD_STATE_TTL_MS } from "chat";
|
|
1320
|
+
import {
|
|
1321
|
+
actorSchema,
|
|
1322
|
+
destinationSchema,
|
|
1323
|
+
sourceSchema
|
|
1324
|
+
} from "@sentry/junior-plugin-api";
|
|
1325
|
+
import { z as z2 } from "zod";
|
|
1326
|
+
|
|
1327
|
+
// src/chat/state/turn-session-keys.ts
|
|
1328
|
+
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
1329
|
+
function agentTurnSessionKey(conversationId, sessionId) {
|
|
1330
|
+
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
1331
|
+
}
|
|
1332
|
+
function agentTurnSessionConversationIndexKey(conversationId) {
|
|
1333
|
+
return `${AGENT_TURN_SESSION_PREFIX}:conversation:${conversationId}:index`;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// src/chat/state/turn-session.ts
|
|
1337
|
+
var AGENT_TURN_SESSION_INDEX_KEY = `${AGENT_TURN_SESSION_PREFIX}:index`;
|
|
1338
|
+
var AGENT_TURN_SESSION_INDEX_MAX_LENGTH = 5e3;
|
|
1339
|
+
var AGENT_TURN_SESSION_TTL_MS = THREAD_STATE_TTL_MS;
|
|
1340
|
+
var agentTurnSessionStatusSchema = z2.enum([
|
|
1341
|
+
"running",
|
|
1342
|
+
"awaiting_resume",
|
|
1343
|
+
"completed",
|
|
1344
|
+
"failed",
|
|
1345
|
+
"abandoned"
|
|
1346
|
+
]);
|
|
1347
|
+
var agentTurnSurfaceSchema = z2.enum([
|
|
1348
|
+
"slack",
|
|
1349
|
+
"api",
|
|
1350
|
+
"scheduler",
|
|
1351
|
+
"internal"
|
|
1352
|
+
]);
|
|
1353
|
+
var agentTurnResumeReasonSchema = z2.enum([
|
|
1354
|
+
"timeout",
|
|
1355
|
+
"auth",
|
|
1356
|
+
"yield",
|
|
1357
|
+
"retry"
|
|
1358
|
+
]);
|
|
1359
|
+
var nonNegativeNumberSchema = z2.number().finite().nonnegative();
|
|
1360
|
+
var seqCursorSchema = z2.number().int().min(-1);
|
|
1361
|
+
var agentTurnSessionSummarySchema = z2.object({
|
|
1362
|
+
channelName: z2.string().min(1).optional(),
|
|
1363
|
+
version: z2.number().int().nonnegative(),
|
|
1364
|
+
conversationId: z2.string().min(1),
|
|
1365
|
+
cumulativeDurationMs: nonNegativeNumberSchema,
|
|
1366
|
+
cumulativeUsage: agentTurnUsageSchema.optional(),
|
|
1367
|
+
destination: destinationSchema.optional(),
|
|
1368
|
+
dispatchId: z2.string().min(1).optional(),
|
|
1369
|
+
dispatchOutcome: z2.enum(["blocked", "completed", "failed"]).optional(),
|
|
1370
|
+
resultMessageId: z2.string().min(1).optional(),
|
|
1371
|
+
source: sourceSchema.optional(),
|
|
1372
|
+
lastProgressAtMs: nonNegativeNumberSchema,
|
|
1373
|
+
loadedSkillNames: z2.array(z2.string()).optional(),
|
|
1374
|
+
modelId: z2.string().min(1).optional(),
|
|
1375
|
+
reasoningLevel: z2.string().min(1).optional(),
|
|
1376
|
+
actor: actorSchema.optional(),
|
|
1377
|
+
resumeReason: agentTurnResumeReasonSchema.optional(),
|
|
1378
|
+
resumedFromSliceId: z2.number().int().nonnegative().optional(),
|
|
1379
|
+
sessionId: z2.string().min(1),
|
|
1380
|
+
sliceId: z2.number().int().nonnegative(),
|
|
1381
|
+
startedAtMs: nonNegativeNumberSchema,
|
|
1382
|
+
state: agentTurnSessionStatusSchema,
|
|
1383
|
+
surface: agentTurnSurfaceSchema.optional(),
|
|
1384
|
+
traceId: z2.string().optional(),
|
|
1385
|
+
updatedAtMs: nonNegativeNumberSchema
|
|
1386
|
+
}).strict();
|
|
1387
|
+
var storedAgentTurnSessionRecordSchema = agentTurnSessionSummarySchema.extend({
|
|
1388
|
+
actors: z2.array(actorSchema).optional(),
|
|
1389
|
+
committedSeq: seqCursorSchema,
|
|
1390
|
+
historyVersion: z2.number().int().nonnegative().optional(),
|
|
1391
|
+
errorMessage: z2.string().optional(),
|
|
1392
|
+
turnStartSeq: seqCursorSchema.optional(),
|
|
1393
|
+
runtimeContext: z2.array(piMessageSchema).optional()
|
|
1394
|
+
}).strict();
|
|
1395
|
+
function conversationExecutionFromSummary(summary) {
|
|
1396
|
+
const status = summary.state === "completed" || summary.state === "abandoned" ? "idle" : summary.state;
|
|
1397
|
+
return {
|
|
1398
|
+
status,
|
|
1399
|
+
runId: summary.sessionId,
|
|
1400
|
+
updatedAtMs: summary.updatedAtMs
|
|
1640
1401
|
};
|
|
1641
1402
|
}
|
|
1642
|
-
function
|
|
1643
|
-
|
|
1644
|
-
return void 0;
|
|
1645
|
-
}
|
|
1646
|
-
const kind = value.kind;
|
|
1647
|
-
const provider = toOptionalString(value.provider);
|
|
1648
|
-
const actorId = toOptionalString(value.actorId);
|
|
1649
|
-
const authSessionId = toOptionalString(value.authSessionId);
|
|
1650
|
-
const scope = toOptionalString(value.scope);
|
|
1651
|
-
const sessionId = toOptionalString(value.sessionId);
|
|
1652
|
-
const linkSentAtMs = toOptionalNumber(value.linkSentAtMs);
|
|
1653
|
-
if (kind !== "mcp" && kind !== "plugin" || !provider || !actorId || kind === "mcp" && !authSessionId || !sessionId || typeof linkSentAtMs !== "number") {
|
|
1654
|
-
return void 0;
|
|
1655
|
-
}
|
|
1656
|
-
const base = {
|
|
1657
|
-
provider,
|
|
1658
|
-
actorId,
|
|
1659
|
-
...scope ? { scope } : {},
|
|
1660
|
-
sessionId,
|
|
1661
|
-
linkSentAtMs
|
|
1662
|
-
};
|
|
1663
|
-
return kind === "mcp" ? { ...base, authSessionId, kind } : { ...base, kind };
|
|
1403
|
+
function sessionLogActor(actor) {
|
|
1404
|
+
return actor?.platform === "slack" ? toStoredSlackActor(actor) : void 0;
|
|
1664
1405
|
}
|
|
1665
|
-
function
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
const
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
const processing = {
|
|
1681
|
-
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
1682
|
-
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
1683
|
-
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
1684
|
-
};
|
|
1685
|
-
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
1686
|
-
const stats = {
|
|
1687
|
-
estimatedContextTokens: toOptionalNumber(rawStats.estimatedContextTokens) ?? base.stats.estimatedContextTokens,
|
|
1688
|
-
totalMessageCount: toOptionalNumber(rawStats.totalMessageCount) ?? messages.length,
|
|
1689
|
-
compactedMessageCount: toOptionalNumber(rawStats.compactedMessageCount) ?? 0,
|
|
1690
|
-
updatedAtMs: toOptionalNumber(rawStats.updatedAtMs) ?? base.stats.updatedAtMs
|
|
1691
|
-
};
|
|
1692
|
-
const rawVision = isRecord(rawConversation.vision) ? rawConversation.vision : {};
|
|
1693
|
-
const rawVisionByFileId = isRecord(rawVision.byFileId) ? rawVision.byFileId : {};
|
|
1694
|
-
const byFileId = {};
|
|
1695
|
-
for (const [fileId, value2] of Object.entries(rawVisionByFileId)) {
|
|
1696
|
-
if (typeof fileId !== "string" || fileId.trim().length === 0) continue;
|
|
1697
|
-
if (!isRecord(value2)) continue;
|
|
1698
|
-
const summary = toOptionalString(value2.summary);
|
|
1699
|
-
const analyzedAtMs = toOptionalNumber(value2.analyzedAtMs);
|
|
1700
|
-
if (!summary || !analyzedAtMs) continue;
|
|
1701
|
-
byFileId[fileId] = {
|
|
1406
|
+
function parseAgentTurnSessionRecord(value) {
|
|
1407
|
+
return storedAgentTurnSessionRecordSchema.parse(value);
|
|
1408
|
+
}
|
|
1409
|
+
function parseAgentTurnSessionSummary(value) {
|
|
1410
|
+
return agentTurnSessionSummarySchema.parse(value);
|
|
1411
|
+
}
|
|
1412
|
+
async function appendAgentTurnSessionSummary(summary, ttlMs) {
|
|
1413
|
+
const stateAdapter = getStateAdapter();
|
|
1414
|
+
await Promise.all([
|
|
1415
|
+
stateAdapter.appendToList(AGENT_TURN_SESSION_INDEX_KEY, summary, {
|
|
1416
|
+
maxLength: AGENT_TURN_SESSION_INDEX_MAX_LENGTH,
|
|
1417
|
+
ttlMs
|
|
1418
|
+
}),
|
|
1419
|
+
stateAdapter.appendToList(
|
|
1420
|
+
agentTurnSessionConversationIndexKey(summary.conversationId),
|
|
1702
1421
|
summary,
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
return {
|
|
1707
|
-
schemaVersion: 1,
|
|
1708
|
-
messages,
|
|
1709
|
-
compactions,
|
|
1710
|
-
backfill,
|
|
1711
|
-
processing,
|
|
1712
|
-
stats,
|
|
1713
|
-
vision: {
|
|
1714
|
-
backfillCompletedAtMs: toOptionalNumber(rawVision.backfillCompletedAtMs),
|
|
1715
|
-
byFileId
|
|
1716
|
-
}
|
|
1717
|
-
};
|
|
1422
|
+
{ ttlMs }
|
|
1423
|
+
)
|
|
1424
|
+
]);
|
|
1718
1425
|
}
|
|
1719
|
-
function
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1426
|
+
async function recordConversationActivityMetadata(args) {
|
|
1427
|
+
const conversationStore = args.conversationStore ?? getConversationStore();
|
|
1428
|
+
const source = args.summary.destination?.platform === "local" ? "local" : args.summary.surface;
|
|
1429
|
+
await conversationStore.recordActivity({
|
|
1430
|
+
activityAtMs: args.summary.updatedAtMs,
|
|
1431
|
+
channelName: args.summary.channelName,
|
|
1432
|
+
conversationId: args.summary.conversationId,
|
|
1433
|
+
destination: args.summary.destination,
|
|
1434
|
+
nowMs: args.nowMs,
|
|
1435
|
+
actor: sessionLogActor(args.summary.actor),
|
|
1436
|
+
source,
|
|
1437
|
+
visibility: args.destinationVisibility
|
|
1438
|
+
});
|
|
1439
|
+
await conversationStore.recordExecution({
|
|
1440
|
+
channelName: args.summary.channelName,
|
|
1441
|
+
conversationId: args.summary.conversationId,
|
|
1442
|
+
createdAtMs: args.summary.startedAtMs,
|
|
1443
|
+
destination: args.summary.destination,
|
|
1444
|
+
execution: conversationExecutionFromSummary(args.summary),
|
|
1445
|
+
lastActivityAtMs: args.summary.updatedAtMs,
|
|
1446
|
+
metrics: {
|
|
1447
|
+
durationMs: args.summary.cumulativeDurationMs,
|
|
1448
|
+
...args.summary.cumulativeUsage ? { usage: args.summary.cumulativeUsage } : {}
|
|
1449
|
+
},
|
|
1450
|
+
actor: sessionLogActor(args.summary.actor),
|
|
1451
|
+
source,
|
|
1452
|
+
updatedAtMs: args.nowMs,
|
|
1453
|
+
visibility: args.destinationVisibility
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
function materializeAgentTurnSessionRecord(stored, piProjection, turnStartMessageIndex, restoreVolatileContext = true) {
|
|
1457
|
+
const piMessages = restoreVolatileContext && (stored.state === "running" || stored.state === "awaiting_resume") ? restoreRuntimeContext(piProjection.messages, stored.runtimeContext) : piProjection.messages;
|
|
1725
1458
|
return {
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1459
|
+
version: stored.version,
|
|
1460
|
+
...stored.channelName ? { channelName: stored.channelName } : {},
|
|
1461
|
+
conversationId: stored.conversationId,
|
|
1462
|
+
sessionId: stored.sessionId,
|
|
1463
|
+
sliceId: stored.sliceId,
|
|
1464
|
+
state: stored.state,
|
|
1465
|
+
startedAtMs: stored.startedAtMs,
|
|
1466
|
+
lastProgressAtMs: stored.lastProgressAtMs,
|
|
1467
|
+
updatedAtMs: stored.updatedAtMs,
|
|
1468
|
+
piMessages,
|
|
1469
|
+
piMessageProvenance: piProjection.provenance,
|
|
1470
|
+
actors: stored.actors ?? instructionActors(piProjection.provenance),
|
|
1471
|
+
cumulativeDurationMs: stored.cumulativeDurationMs,
|
|
1472
|
+
...stored.destination ? { destination: stored.destination } : {},
|
|
1473
|
+
...stored.dispatchId ? { dispatchId: stored.dispatchId } : {},
|
|
1474
|
+
...stored.dispatchOutcome ? { dispatchOutcome: stored.dispatchOutcome } : {},
|
|
1475
|
+
...stored.resultMessageId ? { resultMessageId: stored.resultMessageId } : {},
|
|
1476
|
+
...stored.source ? { source: stored.source } : {},
|
|
1477
|
+
...stored.cumulativeUsage ? { cumulativeUsage: stored.cumulativeUsage } : {},
|
|
1478
|
+
...stored.resumeReason ? { resumeReason: stored.resumeReason } : {},
|
|
1479
|
+
...stored.errorMessage ? { errorMessage: stored.errorMessage } : {},
|
|
1480
|
+
...stored.loadedSkillNames ? { loadedSkillNames: stored.loadedSkillNames } : {},
|
|
1481
|
+
...stored.modelId ? { modelId: stored.modelId } : {},
|
|
1482
|
+
...stored.reasoningLevel ? { reasoningLevel: stored.reasoningLevel } : {},
|
|
1483
|
+
...stored.actor ? { actor: stored.actor } : {},
|
|
1484
|
+
...stored.resumedFromSliceId !== void 0 ? { resumedFromSliceId: stored.resumedFromSliceId } : {},
|
|
1485
|
+
...stored.surface ? { surface: stored.surface } : {},
|
|
1486
|
+
...stored.traceId ? { traceId: stored.traceId } : {},
|
|
1487
|
+
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {}
|
|
1735
1488
|
};
|
|
1736
1489
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1490
|
+
function restoreRuntimeContext(messages, runtimeContext) {
|
|
1491
|
+
if (!runtimeContext || runtimeContext.length === 0) return messages;
|
|
1492
|
+
const restored = [...messages];
|
|
1493
|
+
const unmatchedRuntimeContext = [];
|
|
1494
|
+
for (const runtimeMessage of runtimeContext) {
|
|
1495
|
+
const runtime = runtimeMessage;
|
|
1496
|
+
const targetIndex = restored.findIndex((message) => {
|
|
1497
|
+
const candidate = message;
|
|
1498
|
+
return candidate.role === "user" && candidate.timestamp === runtime.timestamp;
|
|
1499
|
+
});
|
|
1500
|
+
if (targetIndex < 0) {
|
|
1501
|
+
unmatchedRuntimeContext.push(runtimeMessage);
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
const target = restored[targetIndex];
|
|
1505
|
+
restored[targetIndex] = {
|
|
1506
|
+
...restored[targetIndex],
|
|
1507
|
+
content: [
|
|
1508
|
+
...Array.isArray(runtime.content) ? runtime.content : [],
|
|
1509
|
+
...Array.isArray(target.content) ? target.content : []
|
|
1510
|
+
]
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
return [...unmatchedRuntimeContext, ...restored];
|
|
1745
1514
|
}
|
|
1746
|
-
function
|
|
1747
|
-
|
|
1515
|
+
async function getStoredAgentTurnSessionRecord(conversationId, sessionId) {
|
|
1516
|
+
const stateAdapter = getStateAdapter();
|
|
1517
|
+
await stateAdapter.connect();
|
|
1518
|
+
const value = await stateAdapter.get(
|
|
1519
|
+
agentTurnSessionKey(conversationId, sessionId)
|
|
1520
|
+
);
|
|
1521
|
+
return value == null ? void 0 : parseAgentTurnSessionRecord(value);
|
|
1748
1522
|
}
|
|
1749
|
-
function
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1752
|
-
|
|
1523
|
+
async function materializeStoredAgentTurnSessionRecord(conversationId, sessionId, followCurrentReplacement) {
|
|
1524
|
+
const parsed = await getStoredAgentTurnSessionRecord(
|
|
1525
|
+
conversationId,
|
|
1526
|
+
sessionId
|
|
1527
|
+
);
|
|
1528
|
+
if (!parsed) {
|
|
1529
|
+
return void 0;
|
|
1530
|
+
}
|
|
1531
|
+
const pinnedProjection = await loadTurnProjection({
|
|
1532
|
+
conversationId,
|
|
1533
|
+
committedSeq: parsed.committedSeq,
|
|
1534
|
+
// Unfinished records include the current-epoch tail so parked input
|
|
1535
|
+
// appended after the last safe boundary stays model-visible on resume.
|
|
1536
|
+
includeTail: parsed.state === "running" || parsed.state === "awaiting_resume"
|
|
1537
|
+
});
|
|
1538
|
+
if (!pinnedProjection) {
|
|
1539
|
+
return void 0;
|
|
1540
|
+
}
|
|
1541
|
+
const currentHistory = await getConversationEventStore().loadCurrentHistory(conversationId);
|
|
1542
|
+
const currentHistoryVersion = currentHistory.at(-1)?.historyVersion ?? parsed.historyVersion ?? 0;
|
|
1543
|
+
const followsReplacement = followCurrentReplacement && (parsed.state === "running" || parsed.state === "awaiting_resume") && parsed.historyVersion !== void 0 && parsed.historyVersion !== currentHistoryVersion;
|
|
1544
|
+
const piProjection = followsReplacement ? projectConversationEvents(currentHistory) : pinnedProjection;
|
|
1545
|
+
const turnStartMessageIndex = followsReplacement ? 0 : parsed.turnStartSeq === void 0 ? void 0 : piProjection.seqs.filter((seq) => seq <= parsed.turnStartSeq).length;
|
|
1546
|
+
return materializeAgentTurnSessionRecord(
|
|
1547
|
+
parsed,
|
|
1548
|
+
piProjection,
|
|
1549
|
+
turnStartMessageIndex,
|
|
1550
|
+
!followsReplacement && (parsed.historyVersion === void 0 || parsed.historyVersion === currentHistoryVersion)
|
|
1753
1551
|
);
|
|
1754
|
-
return Math.max(1, Math.floor(contextWindow * COMPACTION_TRIGGER_RATIO));
|
|
1755
1552
|
}
|
|
1756
|
-
function
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1553
|
+
async function getAgentTurnSessionRecord(conversationId, sessionId) {
|
|
1554
|
+
return await materializeStoredAgentTurnSessionRecord(
|
|
1555
|
+
conversationId,
|
|
1556
|
+
sessionId,
|
|
1557
|
+
false
|
|
1760
1558
|
);
|
|
1761
|
-
return Math.max(1, Math.floor(contextWindow * CONTEXT_INPUT_LIMIT_RATIO));
|
|
1762
1559
|
}
|
|
1763
|
-
function
|
|
1764
|
-
return
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
const advertisedContextWindow = positiveInteger(
|
|
1769
|
-
model.contextWindow,
|
|
1770
|
-
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
1560
|
+
async function getAgentTurnSessionRecordForResume(conversationId, sessionId) {
|
|
1561
|
+
return await materializeStoredAgentTurnSessionRecord(
|
|
1562
|
+
conversationId,
|
|
1563
|
+
sessionId,
|
|
1564
|
+
true
|
|
1771
1565
|
);
|
|
1566
|
+
}
|
|
1567
|
+
function buildStoredRecord(args) {
|
|
1568
|
+
const nowMs = Date.now();
|
|
1772
1569
|
return {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1570
|
+
version: (args.previousVersion ?? 0) + 1,
|
|
1571
|
+
...args.channelName ? { channelName: args.channelName } : {},
|
|
1572
|
+
conversationId: args.conversationId,
|
|
1573
|
+
sessionId: args.sessionId,
|
|
1574
|
+
sliceId: args.sliceId,
|
|
1575
|
+
state: args.state,
|
|
1576
|
+
startedAtMs: args.startedAtMs ?? nowMs,
|
|
1577
|
+
lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
|
|
1578
|
+
updatedAtMs: nowMs,
|
|
1579
|
+
committedSeq: args.committedSeq,
|
|
1580
|
+
...args.historyVersion !== void 0 ? { historyVersion: args.historyVersion } : {},
|
|
1581
|
+
...args.turnStartSeq !== void 0 ? { turnStartSeq: args.turnStartSeq } : {},
|
|
1582
|
+
...args.runtimeContext && args.runtimeContext.length > 0 ? { runtimeContext: args.runtimeContext } : {},
|
|
1583
|
+
cumulativeDurationMs: args.cumulativeDurationMs,
|
|
1584
|
+
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
1585
|
+
...args.destination ? { destination: args.destination } : {},
|
|
1586
|
+
...args.dispatchId ? { dispatchId: args.dispatchId } : {},
|
|
1587
|
+
...args.dispatchOutcome ? { dispatchOutcome: args.dispatchOutcome } : {},
|
|
1588
|
+
...args.resultMessageId ? { resultMessageId: args.resultMessageId } : {},
|
|
1589
|
+
...args.source ? { source: args.source } : {},
|
|
1590
|
+
...args.actor ? { actor: args.actor } : {},
|
|
1591
|
+
...args.actors ? { actors: args.actors } : {},
|
|
1592
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1593
|
+
...args.modelId ? { modelId: args.modelId } : {},
|
|
1594
|
+
...args.reasoningLevel ? { reasoningLevel: args.reasoningLevel } : {},
|
|
1595
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
1596
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
1597
|
+
...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
|
|
1598
|
+
...args.surface ? { surface: args.surface } : {},
|
|
1599
|
+
...args.traceId ? { traceId: args.traceId } : {}
|
|
1777
1600
|
};
|
|
1778
1601
|
}
|
|
1779
|
-
function
|
|
1780
|
-
|
|
1781
|
-
|
|
1602
|
+
async function setStoredRecord(args) {
|
|
1603
|
+
const stateAdapter = getStateAdapter();
|
|
1604
|
+
await stateAdapter.connect();
|
|
1605
|
+
await recordConversationActivityMetadata({
|
|
1606
|
+
conversationStore: args.conversationStore,
|
|
1607
|
+
destinationVisibility: args.destinationVisibility,
|
|
1608
|
+
nowMs: Date.now(),
|
|
1609
|
+
summary: args.record
|
|
1610
|
+
});
|
|
1611
|
+
await stateAdapter.set(
|
|
1612
|
+
agentTurnSessionKey(args.record.conversationId, args.record.sessionId),
|
|
1613
|
+
args.record,
|
|
1614
|
+
args.ttlMs
|
|
1782
1615
|
);
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1616
|
+
const {
|
|
1617
|
+
actors: _actors,
|
|
1618
|
+
committedSeq: _committedSeq,
|
|
1619
|
+
historyVersion: _historyVersion,
|
|
1620
|
+
errorMessage: _errorMessage,
|
|
1621
|
+
turnStartSeq: _turnStartSeq,
|
|
1622
|
+
runtimeContext: _runtimeContext,
|
|
1623
|
+
...summary
|
|
1624
|
+
} = args.record;
|
|
1625
|
+
await appendAgentTurnSessionSummary(summary, args.ttlMs);
|
|
1626
|
+
return materializeAgentTurnSessionRecord(
|
|
1627
|
+
args.record,
|
|
1628
|
+
{
|
|
1629
|
+
messages: [...args.piMessages],
|
|
1630
|
+
provenance: [...args.piMessageProvenance]
|
|
1631
|
+
},
|
|
1632
|
+
args.turnStartMessageIndex
|
|
1790
1633
|
);
|
|
1791
1634
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
function generateConversationId(prefix) {
|
|
1800
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1801
|
-
}
|
|
1802
|
-
function normalizeConversationText(text) {
|
|
1803
|
-
return text.replace(/\r\n?/g, "\n").trim();
|
|
1804
|
-
}
|
|
1805
|
-
function buildImageContextSuffix(message, conversation) {
|
|
1806
|
-
const byFileId = conversation?.vision.byFileId;
|
|
1807
|
-
const imageFileIds = message.meta?.imageFileIds ?? [];
|
|
1808
|
-
if (!byFileId || imageFileIds.length === 0) {
|
|
1809
|
-
return "";
|
|
1635
|
+
async function updateAgentTurnSessionState(args) {
|
|
1636
|
+
const parsed = await getStoredAgentTurnSessionRecord(
|
|
1637
|
+
args.existing.conversationId,
|
|
1638
|
+
args.existing.sessionId
|
|
1639
|
+
);
|
|
1640
|
+
if (!parsed || parsed.version !== args.existing.version) {
|
|
1641
|
+
return void 0;
|
|
1810
1642
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1643
|
+
return await setStoredRecord({
|
|
1644
|
+
piMessages: args.existing.piMessages,
|
|
1645
|
+
piMessageProvenance: args.existing.piMessageProvenance,
|
|
1646
|
+
ttlMs: AGENT_TURN_SESSION_TTL_MS,
|
|
1647
|
+
...args.existing.turnStartMessageIndex !== void 0 ? { turnStartMessageIndex: args.existing.turnStartMessageIndex } : {},
|
|
1648
|
+
record: buildStoredRecord({
|
|
1649
|
+
conversationId: args.existing.conversationId,
|
|
1650
|
+
sessionId: args.existing.sessionId,
|
|
1651
|
+
sliceId: args.existing.sliceId,
|
|
1652
|
+
state: args.state,
|
|
1653
|
+
committedSeq: parsed.committedSeq,
|
|
1654
|
+
...parsed.historyVersion !== void 0 ? { historyVersion: parsed.historyVersion } : {},
|
|
1655
|
+
...parsed.turnStartSeq !== void 0 ? { turnStartSeq: parsed.turnStartSeq } : {},
|
|
1656
|
+
...parsed.runtimeContext ? { runtimeContext: parsed.runtimeContext } : {},
|
|
1657
|
+
...parsed.channelName ? { channelName: parsed.channelName } : {},
|
|
1658
|
+
startedAtMs: parsed.startedAtMs,
|
|
1659
|
+
lastProgressAtMs: parsed.lastProgressAtMs,
|
|
1660
|
+
previousVersion: parsed.version,
|
|
1661
|
+
cumulativeDurationMs: args.existing.cumulativeDurationMs,
|
|
1662
|
+
...args.existing.cumulativeUsage ? { cumulativeUsage: args.existing.cumulativeUsage } : {},
|
|
1663
|
+
...args.existing.destination ? { destination: args.existing.destination } : {},
|
|
1664
|
+
...args.existing.dispatchId ? { dispatchId: args.existing.dispatchId } : {},
|
|
1665
|
+
...args.existing.dispatchOutcome ? { dispatchOutcome: args.existing.dispatchOutcome } : {},
|
|
1666
|
+
...args.existing.resultMessageId ? { resultMessageId: args.existing.resultMessageId } : {},
|
|
1667
|
+
...args.existing.source ? { source: args.existing.source } : {},
|
|
1668
|
+
...args.existing.loadedSkillNames ? { loadedSkillNames: args.existing.loadedSkillNames } : {},
|
|
1669
|
+
...args.existing.modelId ? { modelId: args.existing.modelId } : {},
|
|
1670
|
+
...args.existing.reasoningLevel ? { reasoningLevel: args.existing.reasoningLevel } : {},
|
|
1671
|
+
...args.existing.actor ? { actor: args.existing.actor } : {},
|
|
1672
|
+
actors: args.existing.actors,
|
|
1673
|
+
...args.existing.resumeReason ? { resumeReason: args.existing.resumeReason } : {},
|
|
1674
|
+
...args.existing.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.existing.resumedFromSliceId } : {},
|
|
1675
|
+
...args.existing.surface ? { surface: args.existing.surface } : {},
|
|
1676
|
+
...args.existing.traceId ? { traceId: args.existing.traceId } : {},
|
|
1677
|
+
...args.errorMessage ?? args.existing.errorMessage ? { errorMessage: args.errorMessage ?? args.existing.errorMessage } : {}
|
|
1678
|
+
})
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
async function upsertAgentTurnSessionRecord(args) {
|
|
1682
|
+
const existingRecord = await getStoredAgentTurnSessionRecord(
|
|
1683
|
+
args.conversationId,
|
|
1684
|
+
args.sessionId
|
|
1685
|
+
);
|
|
1686
|
+
const existingDispatchId = existingRecord?.dispatchId ?? (await listAgentTurnSessionSummariesForConversation(args.conversationId)).find((summary) => summary.sessionId === args.sessionId)?.dispatchId;
|
|
1687
|
+
if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
|
|
1688
|
+
throw new Error(
|
|
1689
|
+
`Turn session ${args.sessionId} dispatchId cannot be changed`
|
|
1690
|
+
);
|
|
1814
1691
|
}
|
|
1815
|
-
|
|
1692
|
+
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
1693
|
+
const instructionActor = args.actor ?? existingRecord?.actor;
|
|
1694
|
+
const commit = await commitMessages({
|
|
1695
|
+
conversationId: args.conversationId,
|
|
1696
|
+
messages: args.piMessages,
|
|
1697
|
+
...instructionActor ? { newMessageProvenance: instructionProvenanceFor(instructionActor) } : {},
|
|
1698
|
+
...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {},
|
|
1699
|
+
...args.turnContexts && args.turnContexts.length > 0 ? {
|
|
1700
|
+
turnContext: {
|
|
1701
|
+
contexts: args.turnContexts,
|
|
1702
|
+
turnId: args.sessionId
|
|
1703
|
+
}
|
|
1704
|
+
} : {}
|
|
1705
|
+
});
|
|
1706
|
+
const durableTurnStartMessageIndex = args.turnStartMessageIndex === void 0 ? void 0 : stripRuntimeTurnContext(
|
|
1707
|
+
args.piMessages.slice(0, args.turnStartMessageIndex)
|
|
1708
|
+
).length;
|
|
1709
|
+
const runtimeContext = retainRuntimeTurnContext(args.piMessages);
|
|
1710
|
+
const retainedRuntimeContext = runtimeContext.length > 0 ? runtimeContext : existingRecord?.historyVersion === commit.historyVersion ? existingRecord.runtimeContext : void 0;
|
|
1711
|
+
const turnStartSeq = durableTurnStartMessageIndex === void 0 ? existingRecord?.turnStartSeq : durableTurnStartMessageIndex <= 0 ? -1 : commit.messageSeqs[durableTurnStartMessageIndex - 1] ?? commit.committedSeq;
|
|
1712
|
+
const turnStartMessageIndex = durableTurnStartMessageIndex ?? (turnStartSeq === void 0 ? void 0 : commit.messageSeqs.filter((seq) => seq <= turnStartSeq).length);
|
|
1713
|
+
return await setStoredRecord({
|
|
1714
|
+
conversationStore: args.conversationStore,
|
|
1715
|
+
destinationVisibility: args.destinationVisibility,
|
|
1716
|
+
piMessages: commit.messages,
|
|
1717
|
+
piMessageProvenance: commit.provenance,
|
|
1718
|
+
ttlMs,
|
|
1719
|
+
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {},
|
|
1720
|
+
record: buildStoredRecord({
|
|
1721
|
+
...args.channelName ?? existingRecord?.channelName ? { channelName: args.channelName ?? existingRecord?.channelName } : {},
|
|
1722
|
+
conversationId: args.conversationId,
|
|
1723
|
+
sessionId: args.sessionId,
|
|
1724
|
+
sliceId: args.sliceId,
|
|
1725
|
+
state: args.state,
|
|
1726
|
+
...existingRecord?.startedAtMs !== void 0 ? { startedAtMs: existingRecord.startedAtMs } : {},
|
|
1727
|
+
...args.lastProgressAtMs !== void 0 ? { lastProgressAtMs: args.lastProgressAtMs } : {},
|
|
1728
|
+
committedSeq: commit.committedSeq,
|
|
1729
|
+
historyVersion: commit.historyVersion,
|
|
1730
|
+
...turnStartSeq !== void 0 ? { turnStartSeq } : {},
|
|
1731
|
+
...retainedRuntimeContext ? { runtimeContext: retainedRuntimeContext } : {},
|
|
1732
|
+
previousVersion: existingRecord?.version,
|
|
1733
|
+
cumulativeDurationMs: args.cumulativeDurationMs ?? existingRecord?.cumulativeDurationMs ?? 0,
|
|
1734
|
+
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
1735
|
+
...args.destination ?? existingRecord?.destination ? { destination: args.destination ?? existingRecord?.destination } : {},
|
|
1736
|
+
...args.dispatchId ?? existingRecord?.dispatchId ? { dispatchId: args.dispatchId ?? existingRecord?.dispatchId } : {},
|
|
1737
|
+
...args.dispatchOutcome ?? existingRecord?.dispatchOutcome ? {
|
|
1738
|
+
dispatchOutcome: args.dispatchOutcome ?? existingRecord?.dispatchOutcome
|
|
1739
|
+
} : {},
|
|
1740
|
+
...args.resultMessageId ?? existingRecord?.resultMessageId ? {
|
|
1741
|
+
resultMessageId: args.resultMessageId ?? existingRecord?.resultMessageId
|
|
1742
|
+
} : {},
|
|
1743
|
+
...args.source ?? existingRecord?.source ? { source: args.source ?? existingRecord?.source } : {},
|
|
1744
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1745
|
+
modelId: args.modelId,
|
|
1746
|
+
...args.reasoningLevel ?? existingRecord?.reasoningLevel ? {
|
|
1747
|
+
reasoningLevel: args.reasoningLevel ?? existingRecord?.reasoningLevel
|
|
1748
|
+
} : {},
|
|
1749
|
+
...args.actor ?? existingRecord?.actor ? { actor: args.actor ?? existingRecord?.actor } : {},
|
|
1750
|
+
actors: instructionActors([
|
|
1751
|
+
...(existingRecord?.actors ?? []).map(instructionProvenanceFor),
|
|
1752
|
+
...(args.actors ?? []).map(instructionProvenanceFor),
|
|
1753
|
+
...commit.provenance
|
|
1754
|
+
]),
|
|
1755
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
1756
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
1757
|
+
...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
|
|
1758
|
+
...args.surface ?? existingRecord?.surface ? { surface: args.surface ?? existingRecord?.surface } : {},
|
|
1759
|
+
...args.traceId ?? existingRecord?.traceId ? { traceId: args.traceId ?? existingRecord?.traceId } : {}
|
|
1760
|
+
})
|
|
1761
|
+
});
|
|
1816
1762
|
}
|
|
1817
|
-
function
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1763
|
+
async function recordAgentTurnSessionSummary(args) {
|
|
1764
|
+
const stored = await getStoredAgentTurnSessionRecord(
|
|
1765
|
+
args.conversationId,
|
|
1766
|
+
args.sessionId
|
|
1767
|
+
);
|
|
1768
|
+
const priorSummary = (await listAgentTurnSessionSummariesForConversation(args.conversationId)).find((summary2) => summary2.sessionId === args.sessionId);
|
|
1769
|
+
const existing = stored ?? priorSummary;
|
|
1770
|
+
const existingDispatchId = existing?.dispatchId;
|
|
1771
|
+
const existingDispatchOutcome = priorSummary?.dispatchOutcome ?? stored?.dispatchOutcome;
|
|
1772
|
+
const existingResultMessageId = priorSummary?.resultMessageId ?? stored?.resultMessageId;
|
|
1773
|
+
if (existingDispatchId && args.dispatchId && existingDispatchId !== args.dispatchId) {
|
|
1774
|
+
throw new Error(
|
|
1775
|
+
`Turn session ${args.sessionId} dispatchId cannot be changed`
|
|
1824
1776
|
);
|
|
1825
1777
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1778
|
+
const nowMs = Date.now();
|
|
1779
|
+
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
1780
|
+
const summary = {
|
|
1781
|
+
version: existing?.version ?? 0,
|
|
1782
|
+
...args.channelName ?? existing?.channelName ? { channelName: args.channelName ?? existing?.channelName } : {},
|
|
1783
|
+
conversationId: args.conversationId,
|
|
1784
|
+
sessionId: args.sessionId,
|
|
1785
|
+
sliceId: args.sliceId,
|
|
1786
|
+
startedAtMs: existing?.startedAtMs ?? args.startedAtMs ?? nowMs,
|
|
1787
|
+
lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
|
|
1788
|
+
state: args.state,
|
|
1789
|
+
updatedAtMs: nowMs,
|
|
1790
|
+
cumulativeDurationMs: args.cumulativeDurationMs ?? existing?.cumulativeDurationMs ?? 0,
|
|
1791
|
+
...args.cumulativeUsage ?? existing?.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage ?? existing?.cumulativeUsage } : {},
|
|
1792
|
+
...args.destination ?? existing?.destination ? { destination: args.destination ?? existing?.destination } : {},
|
|
1793
|
+
...args.dispatchId ?? existingDispatchId ? { dispatchId: args.dispatchId ?? existingDispatchId } : {},
|
|
1794
|
+
...args.dispatchOutcome ?? existingDispatchOutcome ? { dispatchOutcome: args.dispatchOutcome ?? existingDispatchOutcome } : {},
|
|
1795
|
+
...args.resultMessageId ?? existingResultMessageId ? { resultMessageId: args.resultMessageId ?? existingResultMessageId } : {},
|
|
1796
|
+
...args.source ?? existing?.source ? { source: args.source ?? existing?.source } : {},
|
|
1797
|
+
...args.actor ?? existing?.actor ? { actor: args.actor ?? existing?.actor } : {},
|
|
1798
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : existing?.loadedSkillNames ? { loadedSkillNames: existing.loadedSkillNames } : {},
|
|
1799
|
+
...args.modelId ?? existing?.modelId ? { modelId: args.modelId ?? existing?.modelId } : {},
|
|
1800
|
+
...args.reasoningLevel ?? existing?.reasoningLevel ? { reasoningLevel: args.reasoningLevel ?? existing?.reasoningLevel } : {},
|
|
1801
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
1802
|
+
...args.surface ?? existing?.surface ? { surface: args.surface ?? existing?.surface } : {},
|
|
1803
|
+
...args.traceId ?? existing?.traceId ? { traceId: args.traceId ?? existing?.traceId } : {}
|
|
1804
|
+
};
|
|
1805
|
+
await recordConversationActivityMetadata({
|
|
1806
|
+
conversationStore: args.conversationStore,
|
|
1807
|
+
destinationVisibility: args.destinationVisibility,
|
|
1808
|
+
nowMs,
|
|
1809
|
+
summary
|
|
1810
|
+
});
|
|
1811
|
+
await appendAgentTurnSessionSummary(summary, ttlMs);
|
|
1838
1812
|
}
|
|
1839
|
-
function
|
|
1840
|
-
|
|
1841
|
-
|
|
1813
|
+
async function readAgentTurnSessionSummariesFromIndex(key, stateAdapter) {
|
|
1814
|
+
await stateAdapter.connect();
|
|
1815
|
+
const values = await stateAdapter.getList(key);
|
|
1816
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1817
|
+
for (const value of [...values].reverse()) {
|
|
1818
|
+
let summary;
|
|
1819
|
+
try {
|
|
1820
|
+
summary = parseAgentTurnSessionSummary(value);
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
logWarn("agent.turn.session_summary_parse.failed", {
|
|
1823
|
+
"app.state.key": key,
|
|
1824
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1825
|
+
});
|
|
1826
|
+
continue;
|
|
1827
|
+
}
|
|
1828
|
+
const summaryKey = `${summary.conversationId}:${summary.sessionId}`;
|
|
1829
|
+
if (!summaries.has(summaryKey)) {
|
|
1830
|
+
summaries.set(summaryKey, summary);
|
|
1831
|
+
}
|
|
1842
1832
|
}
|
|
1843
|
-
return
|
|
1844
|
-
|
|
1845
|
-
function updateConversationStats(conversation) {
|
|
1846
|
-
const contextText = buildConversationContext(conversation);
|
|
1847
|
-
conversation.stats.estimatedContextTokens = estimateTextTokens(
|
|
1848
|
-
contextText ?? ""
|
|
1833
|
+
return [...summaries.values()].sort(
|
|
1834
|
+
(left, right) => right.updatedAtMs - left.updatedAtMs
|
|
1849
1835
|
);
|
|
1850
|
-
conversation.stats.totalMessageCount = conversation.messages.length;
|
|
1851
|
-
conversation.stats.updatedAtMs = Date.now();
|
|
1852
1836
|
}
|
|
1853
|
-
function
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1856
|
-
)
|
|
1857
|
-
|
|
1858
|
-
conversation.messages[existingIndex] = {
|
|
1859
|
-
...conversation.messages[existingIndex],
|
|
1860
|
-
...message,
|
|
1861
|
-
meta: {
|
|
1862
|
-
...conversation.messages[existingIndex]?.meta,
|
|
1863
|
-
...message.meta
|
|
1864
|
-
}
|
|
1865
|
-
};
|
|
1866
|
-
updateConversationStats(conversation);
|
|
1867
|
-
return message.id;
|
|
1837
|
+
async function listAgentTurnSessionSummariesForConversation(conversationId) {
|
|
1838
|
+
const stateAdapter = getStateAdapter();
|
|
1839
|
+
const summaries = await listBoundedAgentTurnSessionSummariesForConversation(conversationId);
|
|
1840
|
+
if (summaries.length > 0) {
|
|
1841
|
+
return summaries;
|
|
1868
1842
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
function recordDeliveredAssistantMessage(args) {
|
|
1874
|
-
const prefix = `${args.sessionId}:assistant:`;
|
|
1875
|
-
const ordinal = args.conversation.messages.filter(
|
|
1876
|
-
(message) => message.id.startsWith(prefix)
|
|
1877
|
-
).length + 1;
|
|
1878
|
-
const messageId = `${prefix}${ordinal}`;
|
|
1879
|
-
markConversationMessage(args.conversation, args.userMessageId, {
|
|
1880
|
-
replied: true,
|
|
1881
|
-
skippedReason: void 0
|
|
1882
|
-
});
|
|
1883
|
-
upsertConversationMessage(args.conversation, {
|
|
1884
|
-
id: messageId,
|
|
1885
|
-
role: "assistant",
|
|
1886
|
-
text: normalizeConversationText(args.text),
|
|
1887
|
-
createdAtMs: Date.now(),
|
|
1888
|
-
author: {
|
|
1889
|
-
userName: botConfig.userName,
|
|
1890
|
-
isBot: true
|
|
1891
|
-
},
|
|
1892
|
-
meta: {
|
|
1893
|
-
replied: true
|
|
1894
|
-
}
|
|
1895
|
-
});
|
|
1896
|
-
return messageId;
|
|
1843
|
+
return (await readAgentTurnSessionSummariesFromIndex(
|
|
1844
|
+
AGENT_TURN_SESSION_INDEX_KEY,
|
|
1845
|
+
stateAdapter
|
|
1846
|
+
)).filter((summary) => summary.conversationId === conversationId);
|
|
1897
1847
|
}
|
|
1898
|
-
function
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
(
|
|
1848
|
+
async function listBoundedAgentTurnSessionSummariesForConversation(conversationId) {
|
|
1849
|
+
return readAgentTurnSessionSummariesFromIndex(
|
|
1850
|
+
agentTurnSessionConversationIndexKey(conversationId),
|
|
1851
|
+
getStateAdapter()
|
|
1902
1852
|
);
|
|
1903
1853
|
}
|
|
1904
|
-
function
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1854
|
+
async function abandonAgentTurnSessionRecord(args) {
|
|
1855
|
+
const existing = await getAgentTurnSessionRecord(
|
|
1856
|
+
args.conversationId,
|
|
1857
|
+
args.sessionId
|
|
1908
1858
|
);
|
|
1909
|
-
if (
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
};
|
|
1918
|
-
updateConversationStats(conversation);
|
|
1859
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned") {
|
|
1860
|
+
return void 0;
|
|
1861
|
+
}
|
|
1862
|
+
return await updateAgentTurnSessionState({
|
|
1863
|
+
existing,
|
|
1864
|
+
state: "abandoned",
|
|
1865
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
1866
|
+
});
|
|
1919
1867
|
}
|
|
1920
|
-
function
|
|
1921
|
-
const
|
|
1922
|
-
|
|
1868
|
+
async function failAgentTurnSessionRecord(args) {
|
|
1869
|
+
const existing = await getAgentTurnSessionRecord(
|
|
1870
|
+
args.conversationId,
|
|
1871
|
+
args.sessionId
|
|
1923
1872
|
);
|
|
1924
|
-
if (
|
|
1873
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
|
|
1925
1874
|
return void 0;
|
|
1926
1875
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1876
|
+
return await updateAgentTurnSessionState({
|
|
1877
|
+
existing,
|
|
1878
|
+
state: "failed",
|
|
1879
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/chat/services/turn-limit.ts
|
|
1884
|
+
var TurnSliceLimitExceededError = class extends Error {
|
|
1885
|
+
constructor(maxSlices) {
|
|
1886
|
+
super(`Agent turn exceeded execution limit (${maxSlices} slices)`);
|
|
1887
|
+
this.name = "TurnSliceLimitExceededError";
|
|
1938
1888
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1889
|
+
};
|
|
1890
|
+
function buildTurnLimitResponse(eventId) {
|
|
1891
|
+
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}\`.`;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// src/chat/services/turn-session-record.ts
|
|
1895
|
+
function logSessionRecordError(error, eventName, args, attributes) {
|
|
1896
|
+
logException(error, eventName, {
|
|
1897
|
+
"app.ai.resume_conversation_id": args.conversationId,
|
|
1898
|
+
"app.ai.resume_session_id": args.sessionId,
|
|
1899
|
+
...attributes
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
function addDurationMs(prior, current) {
|
|
1903
|
+
const total = [prior, current].reduce((sum, value) => {
|
|
1904
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1905
|
+
return sum;
|
|
1954
1906
|
}
|
|
1955
|
-
|
|
1956
|
-
}
|
|
1957
|
-
return
|
|
1907
|
+
return (sum ?? 0) + Math.max(0, Math.floor(value));
|
|
1908
|
+
}, void 0);
|
|
1909
|
+
return total;
|
|
1958
1910
|
}
|
|
1959
|
-
function
|
|
1960
|
-
|
|
1961
|
-
|
|
1911
|
+
function resumableBoundary(messages, fallbackMessages) {
|
|
1912
|
+
const current = trimTrailingAssistantMessages(messages);
|
|
1913
|
+
if (current.length > 0 && isContinuablePiBoundary(current)) {
|
|
1914
|
+
return current;
|
|
1962
1915
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
);
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1916
|
+
return trimTrailingAssistantMessages(fallbackMessages ?? []);
|
|
1917
|
+
}
|
|
1918
|
+
async function loadTurnSessionRecord(ctx) {
|
|
1919
|
+
const existingSessionRecord = await getAgentTurnSessionRecordForResume(
|
|
1920
|
+
ctx.conversationId,
|
|
1921
|
+
ctx.sessionId
|
|
1922
|
+
);
|
|
1923
|
+
const hasAwaitingResumeRecord = Boolean(
|
|
1924
|
+
existingSessionRecord && existingSessionRecord.state === "awaiting_resume"
|
|
1925
|
+
);
|
|
1926
|
+
return {
|
|
1927
|
+
resumedFromSessionRecord: hasAwaitingResumeRecord,
|
|
1928
|
+
currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
|
|
1929
|
+
existingSessionRecord
|
|
1975
1930
|
};
|
|
1976
|
-
return [compacted, ...compactions.slice(overflowCount)];
|
|
1977
1931
|
}
|
|
1978
|
-
async function
|
|
1979
|
-
|
|
1932
|
+
async function persistRunningSessionRecord(args) {
|
|
1933
|
+
if (args.messages.length === 0 || !isContinuablePiBoundary(args.messages)) {
|
|
1934
|
+
return false;
|
|
1935
|
+
}
|
|
1980
1936
|
try {
|
|
1981
|
-
const
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
runId: context.runId ?? ""
|
|
2009
|
-
}
|
|
1937
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
1938
|
+
args.conversationId,
|
|
1939
|
+
args.sessionId
|
|
1940
|
+
);
|
|
1941
|
+
await upsertAgentTurnSessionRecord({
|
|
1942
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1943
|
+
conversationId: args.conversationId,
|
|
1944
|
+
cumulativeDurationMs: latestSessionRecord?.cumulativeDurationMs,
|
|
1945
|
+
cumulativeUsage: latestSessionRecord?.cumulativeUsage,
|
|
1946
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
1947
|
+
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
1948
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
1949
|
+
sessionId: args.sessionId,
|
|
1950
|
+
sliceId: args.sliceId,
|
|
1951
|
+
state: "running",
|
|
1952
|
+
piMessages: args.messages,
|
|
1953
|
+
...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {},
|
|
1954
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
1955
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
1956
|
+
modelId: args.modelId,
|
|
1957
|
+
...args.reasoningLevel ? { reasoningLevel: args.reasoningLevel } : {},
|
|
1958
|
+
...args.turnContexts ? { turnContexts: args.turnContexts } : {},
|
|
1959
|
+
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
1960
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
1961
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
1962
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
1963
|
+
} : {}
|
|
2010
1964
|
});
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
"conversation_compaction_summary_failed",
|
|
2018
|
-
{
|
|
2019
|
-
messageConversationId: context.threadId,
|
|
2020
|
-
userId: context.actorId,
|
|
2021
|
-
destinationName: context.channelId,
|
|
2022
|
-
runId: context.runId,
|
|
2023
|
-
assistantUserName: botConfig.userName,
|
|
2024
|
-
modelId: botConfig.fastModelId
|
|
2025
|
-
},
|
|
1965
|
+
return true;
|
|
1966
|
+
} catch (recordError) {
|
|
1967
|
+
logSessionRecordError(
|
|
1968
|
+
recordError,
|
|
1969
|
+
"agent.turn.running_session_record.failed",
|
|
1970
|
+
args,
|
|
2026
1971
|
{
|
|
2027
|
-
"
|
|
2028
|
-
|
|
2029
|
-
},
|
|
2030
|
-
"Compaction summarization failed; using fallback summary"
|
|
1972
|
+
"app.ai.resume_slice_id": args.sliceId
|
|
1973
|
+
}
|
|
2031
1974
|
);
|
|
1975
|
+
return false;
|
|
2032
1976
|
}
|
|
2033
|
-
return transcript.slice(0, 2800);
|
|
2034
1977
|
}
|
|
2035
|
-
async function
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
1978
|
+
async function persistCompletedSessionRecord(args) {
|
|
1979
|
+
let latestSessionRecord;
|
|
1980
|
+
await persistWithRetry(async () => {
|
|
1981
|
+
latestSessionRecord = await getAgentTurnSessionRecord(
|
|
1982
|
+
args.conversationId,
|
|
1983
|
+
args.sessionId
|
|
1984
|
+
);
|
|
1985
|
+
});
|
|
1986
|
+
const sliceId = args.sliceId ?? latestSessionRecord?.sliceId;
|
|
1987
|
+
if (sliceId === void 0) {
|
|
1988
|
+
throw new Error(
|
|
1989
|
+
"Completed session record requires a slice id from the caller or the latest stored record"
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
const modelId = args.modelId;
|
|
1993
|
+
const reasoningLevel = args.reasoningLevel ?? latestSessionRecord?.reasoningLevel;
|
|
1994
|
+
const target = {
|
|
1995
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
1996
|
+
conversationId: args.conversationId,
|
|
1997
|
+
cumulativeDurationMs: addDurationMs(
|
|
1998
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
1999
|
+
args.currentDurationMs
|
|
2000
|
+
),
|
|
2001
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
2002
|
+
latestSessionRecord?.cumulativeUsage,
|
|
2003
|
+
args.currentUsage
|
|
2004
|
+
),
|
|
2005
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
2006
|
+
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
2007
|
+
...args.dispatchOutcome ?? latestSessionRecord?.dispatchOutcome ? {
|
|
2008
|
+
dispatchOutcome: args.dispatchOutcome ?? latestSessionRecord?.dispatchOutcome
|
|
2009
|
+
} : {},
|
|
2010
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
2011
|
+
...args.resultMessageId ?? latestSessionRecord?.resultMessageId ? {
|
|
2012
|
+
resultMessageId: args.resultMessageId ?? latestSessionRecord?.resultMessageId
|
|
2013
|
+
} : {},
|
|
2014
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
2015
|
+
...args.destinationVisibility ? { destinationVisibility: args.destinationVisibility } : {},
|
|
2016
|
+
sessionId: args.sessionId,
|
|
2017
|
+
sliceId,
|
|
2018
|
+
state: "completed",
|
|
2019
|
+
piMessages: args.allMessages,
|
|
2020
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
2021
|
+
...args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames ? {
|
|
2022
|
+
loadedSkillNames: args.loadedSkillNames ?? latestSessionRecord?.loadedSkillNames
|
|
2023
|
+
} : {},
|
|
2024
|
+
modelId,
|
|
2025
|
+
...reasoningLevel ? { reasoningLevel } : {},
|
|
2026
|
+
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
2027
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
2028
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
2029
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
2030
|
+
} : {}
|
|
2031
|
+
};
|
|
2032
|
+
await persistWithRetry(async () => {
|
|
2033
|
+
await upsertAgentTurnSessionRecord(target);
|
|
2055
2034
|
});
|
|
2056
|
-
return result.text.trim().slice(0, 60);
|
|
2057
2035
|
}
|
|
2058
|
-
function
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2036
|
+
async function completeDeliveredTurn(args) {
|
|
2037
|
+
await persistCompletedSessionRecord({
|
|
2038
|
+
channelName: args.channelName,
|
|
2039
|
+
conversationId: args.conversationId,
|
|
2040
|
+
currentDurationMs: args.durationMs,
|
|
2041
|
+
currentUsage: args.usage,
|
|
2042
|
+
destination: args.destination,
|
|
2043
|
+
destinationVisibility: args.destinationVisibility,
|
|
2044
|
+
dispatchId: args.dispatchId,
|
|
2045
|
+
dispatchOutcome: args.dispatchOutcome,
|
|
2046
|
+
errorMessage: args.errorMessage,
|
|
2047
|
+
resultMessageId: args.resultMessageId,
|
|
2048
|
+
source: args.source,
|
|
2049
|
+
sessionId: args.sessionId,
|
|
2050
|
+
sliceId: args.sliceId,
|
|
2051
|
+
allMessages: args.messages,
|
|
2052
|
+
loadedSkillNames: args.loadedSkillNames,
|
|
2053
|
+
modelId: args.modelId,
|
|
2054
|
+
actor: args.actor,
|
|
2055
|
+
reasoningLevel: args.reasoningLevel,
|
|
2056
|
+
surface: args.surface,
|
|
2057
|
+
turnStartMessageIndex: args.turnStartMessageIndex
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
async function persistAuthPauseSessionRecord(args) {
|
|
2061
|
+
const nextSliceId = args.currentSliceId + 1;
|
|
2062
|
+
try {
|
|
2063
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
2064
|
+
args.conversationId,
|
|
2065
|
+
args.sessionId
|
|
2066
|
+
);
|
|
2067
|
+
const piMessages = resumableBoundary(
|
|
2068
|
+
args.messages,
|
|
2069
|
+
latestSessionRecord?.piMessages
|
|
2070
|
+
);
|
|
2071
|
+
if (piMessages.length > 0 && !isContinuablePiBoundary(piMessages)) {
|
|
2072
|
+
return void 0;
|
|
2074
2073
|
}
|
|
2074
|
+
return await upsertAgentTurnSessionRecord({
|
|
2075
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
2076
|
+
conversationId: args.conversationId,
|
|
2077
|
+
cumulativeDurationMs: addDurationMs(
|
|
2078
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
2079
|
+
args.currentDurationMs
|
|
2080
|
+
),
|
|
2081
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
2082
|
+
latestSessionRecord?.cumulativeUsage,
|
|
2083
|
+
args.currentUsage
|
|
2084
|
+
),
|
|
2085
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
2086
|
+
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
2087
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
2088
|
+
sessionId: args.sessionId,
|
|
2089
|
+
sliceId: nextSliceId,
|
|
2090
|
+
state: "awaiting_resume",
|
|
2091
|
+
piMessages,
|
|
2092
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
2093
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
2094
|
+
modelId: args.modelId,
|
|
2095
|
+
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
2096
|
+
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
2097
|
+
} : {},
|
|
2098
|
+
resumeReason: "auth",
|
|
2099
|
+
resumedFromSliceId: args.currentSliceId,
|
|
2100
|
+
errorMessage: args.errorMessage,
|
|
2101
|
+
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
2102
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
2103
|
+
});
|
|
2104
|
+
} catch (recordError) {
|
|
2105
|
+
logSessionRecordError(
|
|
2106
|
+
recordError,
|
|
2107
|
+
"agent.turn.auth_resume_session_record.failed",
|
|
2108
|
+
args,
|
|
2109
|
+
{
|
|
2110
|
+
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
2111
|
+
"app.ai.resume_next_slice_id": nextSliceId
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2075
2114
|
}
|
|
2076
|
-
return
|
|
2115
|
+
return void 0;
|
|
2077
2116
|
}
|
|
2078
|
-
async function
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
const triggerTokens = getConversationContextCompactionTriggerTokens();
|
|
2085
|
-
const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
|
|
2086
|
-
while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
|
|
2087
|
-
const compactCount = Math.min(
|
|
2088
|
-
CONTEXT_COMPACTION_BATCH_SIZE,
|
|
2089
|
-
conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
|
|
2117
|
+
async function persistContinuationSessionRecord(args) {
|
|
2118
|
+
const nextSliceId = args.currentSliceId + 1;
|
|
2119
|
+
try {
|
|
2120
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
2121
|
+
args.conversationId,
|
|
2122
|
+
args.sessionId
|
|
2090
2123
|
);
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
const compactedChunk = conversation.messages.slice(0, compactCount);
|
|
2095
|
-
const summary = await summarizeConversationChunk(
|
|
2096
|
-
compactedChunk,
|
|
2097
|
-
conversation,
|
|
2098
|
-
context,
|
|
2099
|
-
deps
|
|
2124
|
+
const piMessages = resumableBoundary(
|
|
2125
|
+
args.messages,
|
|
2126
|
+
latestSessionRecord?.piMessages
|
|
2100
2127
|
);
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
createdAtMs: Date.now(),
|
|
2104
|
-
summary,
|
|
2105
|
-
coveredMessageCount: compactedChunk.length
|
|
2106
|
-
});
|
|
2107
|
-
conversation.compactions = pruneCompactions(conversation.compactions);
|
|
2108
|
-
conversation.messages = conversation.messages.slice(compactCount);
|
|
2109
|
-
conversation.stats.compactedMessageCount += compactCount;
|
|
2110
|
-
updateConversationStats(conversation);
|
|
2111
|
-
estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
2112
|
-
setSpanAttributes({
|
|
2113
|
-
"app.compaction_messages_covered": compactCount,
|
|
2114
|
-
"app.compaction.trigger_tokens": triggerTokens,
|
|
2115
|
-
"app.compaction.target_tokens": targetTokens,
|
|
2116
|
-
"app.context_tokens_estimated": estimatedTokens
|
|
2117
|
-
});
|
|
2118
|
-
if (estimatedTokens <= targetTokens) {
|
|
2119
|
-
break;
|
|
2128
|
+
if (piMessages.length === 0 || !isContinuablePiBoundary(piMessages)) {
|
|
2129
|
+
return void 0;
|
|
2120
2130
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2131
|
+
const cumulativeDurationMs = addDurationMs(
|
|
2132
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
2133
|
+
args.currentDurationMs
|
|
2134
|
+
);
|
|
2135
|
+
const cumulativeUsage = addAgentTurnUsage(
|
|
2136
|
+
latestSessionRecord?.cumulativeUsage,
|
|
2137
|
+
args.currentUsage
|
|
2138
|
+
);
|
|
2139
|
+
if (nextSliceId > botConfig.maxSlicesPerTurn) {
|
|
2140
|
+
return await upsertAgentTurnSessionRecord({
|
|
2141
|
+
...args.channelName ?? latestSessionRecord?.channelName ? {
|
|
2142
|
+
channelName: args.channelName ?? latestSessionRecord?.channelName
|
|
2143
|
+
} : {},
|
|
2144
|
+
conversationId: args.conversationId,
|
|
2145
|
+
cumulativeDurationMs,
|
|
2146
|
+
cumulativeUsage,
|
|
2147
|
+
...args.destination ?? latestSessionRecord?.destination ? {
|
|
2148
|
+
destination: args.destination ?? latestSessionRecord?.destination
|
|
2149
|
+
} : {},
|
|
2150
|
+
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
2151
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
2152
|
+
sessionId: args.sessionId,
|
|
2153
|
+
sliceId: args.currentSliceId,
|
|
2154
|
+
state: "failed",
|
|
2155
|
+
piMessages,
|
|
2156
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
2157
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
2158
|
+
modelId: args.modelId,
|
|
2159
|
+
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
2160
|
+
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
2161
|
+
} : {},
|
|
2162
|
+
resumeReason: args.resumeReason,
|
|
2163
|
+
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
2164
|
+
errorMessage: new TurnSliceLimitExceededError(
|
|
2165
|
+
botConfig.maxSlicesPerTurn
|
|
2166
|
+
).message,
|
|
2167
|
+
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
2168
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
2169
|
+
});
|
|
2147
2170
|
}
|
|
2171
|
+
return await upsertAgentTurnSessionRecord({
|
|
2172
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
2173
|
+
conversationId: args.conversationId,
|
|
2174
|
+
cumulativeDurationMs,
|
|
2175
|
+
cumulativeUsage,
|
|
2176
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
2177
|
+
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
2178
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
2179
|
+
sessionId: args.sessionId,
|
|
2180
|
+
sliceId: nextSliceId,
|
|
2181
|
+
state: "awaiting_resume",
|
|
2182
|
+
piMessages,
|
|
2183
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
2184
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
2185
|
+
modelId: args.modelId,
|
|
2186
|
+
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
2187
|
+
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
2188
|
+
} : {},
|
|
2189
|
+
resumeReason: args.resumeReason,
|
|
2190
|
+
resumedFromSliceId: args.currentSliceId,
|
|
2191
|
+
errorMessage: args.errorMessage,
|
|
2192
|
+
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
2193
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
2194
|
+
});
|
|
2195
|
+
} catch (recordError) {
|
|
2196
|
+
logSessionRecordError(
|
|
2197
|
+
recordError,
|
|
2198
|
+
"agent.continue.session_record.failed",
|
|
2199
|
+
args,
|
|
2200
|
+
{
|
|
2201
|
+
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
2202
|
+
"app.ai.resume_next_slice_id": nextSliceId
|
|
2203
|
+
}
|
|
2204
|
+
);
|
|
2205
|
+
return void 0;
|
|
2148
2206
|
}
|
|
2149
|
-
return [];
|
|
2150
2207
|
}
|
|
2151
|
-
async function
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
return;
|
|
2157
|
-
}
|
|
2158
|
-
const liveMessageIds = new Set(
|
|
2159
|
-
args.conversation.messages.map((message) => message.id)
|
|
2160
|
-
);
|
|
2161
|
-
const historyFromSeq = history.events.find(
|
|
2162
|
-
(event) => event.data.type === "message" && liveMessageIds.has(event.data.messageId)
|
|
2163
|
-
)?.seq;
|
|
2164
|
-
const nextHistoryFromSeq = historyFromSeq ?? Math.max(history.historyFromSeq, (history.events.at(-1)?.seq ?? -1) + 1);
|
|
2165
|
-
await eventStore.append(args.conversationId, [
|
|
2166
|
-
{
|
|
2167
|
-
data: {
|
|
2168
|
-
type: "messages_summarized",
|
|
2169
|
-
historyFromSeq: nextHistoryFromSeq,
|
|
2170
|
-
compactions: args.conversation.compactions
|
|
2171
|
-
},
|
|
2172
|
-
createdAtMs: args.conversation.compactions.at(-1)?.createdAtMs ?? Date.now()
|
|
2208
|
+
async function persistYieldSessionRecord(args) {
|
|
2209
|
+
try {
|
|
2210
|
+
const piMessages = [...args.messages];
|
|
2211
|
+
if (piMessages.length === 0 || !isContinuablePiBoundary(piMessages)) {
|
|
2212
|
+
return void 0;
|
|
2173
2213
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
throw missingBaselineError(event);
|
|
2214
|
+
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
2215
|
+
args.conversationId,
|
|
2216
|
+
args.sessionId
|
|
2217
|
+
);
|
|
2218
|
+
return await upsertAgentTurnSessionRecord({
|
|
2219
|
+
...args.channelName ?? latestSessionRecord?.channelName ? { channelName: args.channelName ?? latestSessionRecord?.channelName } : {},
|
|
2220
|
+
conversationId: args.conversationId,
|
|
2221
|
+
cumulativeDurationMs: addDurationMs(
|
|
2222
|
+
latestSessionRecord?.cumulativeDurationMs,
|
|
2223
|
+
args.currentDurationMs
|
|
2224
|
+
),
|
|
2225
|
+
cumulativeUsage: addAgentTurnUsage(
|
|
2226
|
+
latestSessionRecord?.cumulativeUsage,
|
|
2227
|
+
args.currentUsage
|
|
2228
|
+
),
|
|
2229
|
+
...args.destination ?? latestSessionRecord?.destination ? { destination: args.destination ?? latestSessionRecord?.destination } : {},
|
|
2230
|
+
...args.dispatchId ?? latestSessionRecord?.dispatchId ? { dispatchId: args.dispatchId ?? latestSessionRecord?.dispatchId } : {},
|
|
2231
|
+
...args.source ?? latestSessionRecord?.source ? { source: args.source ?? latestSessionRecord?.source } : {},
|
|
2232
|
+
sessionId: args.sessionId,
|
|
2233
|
+
sliceId: args.currentSliceId,
|
|
2234
|
+
state: "awaiting_resume",
|
|
2235
|
+
piMessages,
|
|
2236
|
+
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
2237
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
2238
|
+
modelId: args.modelId,
|
|
2239
|
+
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
2240
|
+
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
2241
|
+
} : {},
|
|
2242
|
+
resumeReason: "yield",
|
|
2243
|
+
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
2244
|
+
errorMessage: args.errorMessage,
|
|
2245
|
+
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
2246
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
2247
|
+
});
|
|
2248
|
+
} catch (recordError) {
|
|
2249
|
+
logSessionRecordError(
|
|
2250
|
+
recordError,
|
|
2251
|
+
"agent.turn.yield_session_record.failed",
|
|
2252
|
+
args,
|
|
2253
|
+
{
|
|
2254
|
+
"app.ai.resume_slice_id": args.currentSliceId
|
|
2216
2255
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
...message,
|
|
2220
|
-
createdAtMs: current?.message.createdAtMs ?? message.createdAtMs
|
|
2221
|
-
},
|
|
2222
|
-
...current?.repliedAtMs === void 0 ? {} : { repliedAtMs: current.repliedAtMs }
|
|
2223
|
-
});
|
|
2224
|
-
continue;
|
|
2225
|
-
}
|
|
2226
|
-
if (!current) {
|
|
2227
|
-
if (history.historyFromSeq > 0) continue;
|
|
2228
|
-
throw missingBaselineError(event);
|
|
2229
|
-
}
|
|
2230
|
-
current.repliedAtMs ??= event.createdAtMs;
|
|
2256
|
+
);
|
|
2257
|
+
return void 0;
|
|
2231
2258
|
}
|
|
2232
|
-
return [...byId.values()].map(({ message, repliedAtMs }) => {
|
|
2233
|
-
if (repliedAtMs === void 0) return message;
|
|
2234
|
-
return {
|
|
2235
|
-
...message,
|
|
2236
|
-
meta: { ...message.meta ?? {}, replied: true }
|
|
2237
|
-
};
|
|
2238
|
-
});
|
|
2239
2259
|
}
|
|
2240
2260
|
|
|
2241
|
-
// src/chat/
|
|
2242
|
-
function
|
|
2243
|
-
const
|
|
2244
|
-
if (message.author) meta.author = message.author;
|
|
2245
|
-
const { replied, ...restMeta } = message.meta ?? {};
|
|
2246
|
-
Object.assign(meta, restMeta);
|
|
2247
|
-
if (replied === false) meta.replied = false;
|
|
2261
|
+
// src/chat/state/conversation.ts
|
|
2262
|
+
function defaultConversationState() {
|
|
2263
|
+
const nowMs = Date.now();
|
|
2248
2264
|
return {
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2265
|
+
schemaVersion: 1,
|
|
2266
|
+
messages: [],
|
|
2267
|
+
compactions: [],
|
|
2268
|
+
backfill: {},
|
|
2269
|
+
processing: {},
|
|
2270
|
+
stats: {
|
|
2271
|
+
estimatedContextTokens: 0,
|
|
2272
|
+
totalMessageCount: 0,
|
|
2273
|
+
compactedMessageCount: 0,
|
|
2274
|
+
updatedAtMs: nowMs
|
|
2275
|
+
},
|
|
2276
|
+
vision: {
|
|
2277
|
+
byFileId: {}
|
|
2278
|
+
}
|
|
2254
2279
|
};
|
|
2255
2280
|
}
|
|
2256
|
-
function
|
|
2257
|
-
if (
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
);
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2281
|
+
function coercePendingAuthState(value) {
|
|
2282
|
+
if (!isRecord(value)) {
|
|
2283
|
+
return void 0;
|
|
2284
|
+
}
|
|
2285
|
+
const kind = value.kind;
|
|
2286
|
+
const provider = toOptionalString(value.provider);
|
|
2287
|
+
const actorId = toOptionalString(value.actorId);
|
|
2288
|
+
const authSessionId = toOptionalString(value.authSessionId);
|
|
2289
|
+
const scope = toOptionalString(value.scope);
|
|
2290
|
+
const sessionId = toOptionalString(value.sessionId);
|
|
2291
|
+
const linkSentAtMs = toOptionalNumber(value.linkSentAtMs);
|
|
2292
|
+
if (kind !== "mcp" && kind !== "plugin" || !provider || !actorId || kind === "mcp" && !authSessionId || !sessionId || typeof linkSentAtMs !== "number") {
|
|
2293
|
+
return void 0;
|
|
2294
|
+
}
|
|
2295
|
+
const base = {
|
|
2296
|
+
provider,
|
|
2297
|
+
actorId,
|
|
2298
|
+
...scope ? { scope } : {},
|
|
2299
|
+
sessionId,
|
|
2300
|
+
linkSentAtMs
|
|
2301
|
+
};
|
|
2302
|
+
return kind === "mcp" ? { ...base, authSessionId, kind } : { ...base, kind };
|
|
2265
2303
|
}
|
|
2266
|
-
|
|
2267
|
-
if (!
|
|
2268
|
-
|
|
2269
|
-
return;
|
|
2304
|
+
function coerceThreadConversationState(value) {
|
|
2305
|
+
if (!isRecord(value)) {
|
|
2306
|
+
return defaultConversationState();
|
|
2270
2307
|
}
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
);
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2308
|
+
const root = value;
|
|
2309
|
+
const rawConversation = isRecord(root.conversation) ? root.conversation : {};
|
|
2310
|
+
const base = defaultConversationState();
|
|
2311
|
+
const messages = [];
|
|
2312
|
+
const compactions = [];
|
|
2313
|
+
const rawBackfill = isRecord(rawConversation.backfill) ? rawConversation.backfill : {};
|
|
2314
|
+
const backfill = {
|
|
2315
|
+
completedAtMs: toOptionalNumber(rawBackfill.completedAtMs),
|
|
2316
|
+
source: rawBackfill.source === "recent_messages" || rawBackfill.source === "thread_fetch" ? rawBackfill.source : void 0
|
|
2317
|
+
};
|
|
2318
|
+
const rawProcessing = isRecord(rawConversation.processing) ? rawConversation.processing : {};
|
|
2319
|
+
const processing = {
|
|
2320
|
+
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
2321
|
+
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
2322
|
+
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
2323
|
+
};
|
|
2324
|
+
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
2325
|
+
const stats = {
|
|
2326
|
+
estimatedContextTokens: toOptionalNumber(rawStats.estimatedContextTokens) ?? base.stats.estimatedContextTokens,
|
|
2327
|
+
totalMessageCount: toOptionalNumber(rawStats.totalMessageCount) ?? messages.length,
|
|
2328
|
+
compactedMessageCount: toOptionalNumber(rawStats.compactedMessageCount) ?? 0,
|
|
2329
|
+
updatedAtMs: toOptionalNumber(rawStats.updatedAtMs) ?? base.stats.updatedAtMs
|
|
2330
|
+
};
|
|
2331
|
+
const rawVision = isRecord(rawConversation.vision) ? rawConversation.vision : {};
|
|
2332
|
+
const rawVisionByFileId = isRecord(rawVision.byFileId) ? rawVision.byFileId : {};
|
|
2333
|
+
const byFileId = {};
|
|
2334
|
+
for (const [fileId, value2] of Object.entries(rawVisionByFileId)) {
|
|
2335
|
+
if (typeof fileId !== "string" || fileId.trim().length === 0) continue;
|
|
2336
|
+
if (!isRecord(value2)) continue;
|
|
2337
|
+
const summary = toOptionalString(value2.summary);
|
|
2338
|
+
const analyzedAtMs = toOptionalNumber(value2.analyzedAtMs);
|
|
2339
|
+
if (!summary || !analyzedAtMs) continue;
|
|
2340
|
+
byFileId[fileId] = {
|
|
2341
|
+
summary,
|
|
2342
|
+
analyzedAtMs
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
return {
|
|
2346
|
+
schemaVersion: 1,
|
|
2347
|
+
messages,
|
|
2348
|
+
compactions,
|
|
2349
|
+
backfill,
|
|
2350
|
+
processing,
|
|
2351
|
+
stats,
|
|
2352
|
+
vision: {
|
|
2353
|
+
backfillCompletedAtMs: toOptionalNumber(rawVision.backfillCompletedAtMs),
|
|
2354
|
+
byFileId
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2277
2357
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
const existing = existingMessages.get(stored.messageId);
|
|
2293
|
-
const changed = existing !== void 0 && !isDeepStrictEqual3(toStoredConversationMessage(existing), stored);
|
|
2294
|
-
const messageEvent = existing ? changed ? [
|
|
2295
|
-
{
|
|
2296
|
-
idempotencyKey: `message:${stored.messageId}:update:${messageStateKey(stored)}`,
|
|
2297
|
-
createdAtMs: handledAtMs,
|
|
2298
|
-
data: {
|
|
2299
|
-
type: "message_updated",
|
|
2300
|
-
messageId: stored.messageId,
|
|
2301
|
-
role: stored.role,
|
|
2302
|
-
text: stored.text,
|
|
2303
|
-
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
|
|
2304
|
-
...stored.meta ? { meta: stored.meta } : {}
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
] : [] : [
|
|
2308
|
-
{
|
|
2309
|
-
idempotencyKey: `message:${stored.messageId}`,
|
|
2310
|
-
createdAtMs: stored.createdAtMs,
|
|
2311
|
-
data: {
|
|
2312
|
-
type: "message",
|
|
2313
|
-
messageId: stored.messageId,
|
|
2314
|
-
role: stored.role,
|
|
2315
|
-
text: stored.text,
|
|
2316
|
-
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
|
|
2317
|
-
...stored.meta ? { meta: stored.meta } : {}
|
|
2318
|
-
}
|
|
2358
|
+
function buildConversationStatePatch(conversation) {
|
|
2359
|
+
const {
|
|
2360
|
+
compactions: _compactions,
|
|
2361
|
+
messages: _messages,
|
|
2362
|
+
...scratch
|
|
2363
|
+
} = conversation;
|
|
2364
|
+
return {
|
|
2365
|
+
conversation: {
|
|
2366
|
+
...scratch,
|
|
2367
|
+
schemaVersion: 1,
|
|
2368
|
+
stats: {
|
|
2369
|
+
...conversation.stats,
|
|
2370
|
+
totalMessageCount: conversation.messages.length,
|
|
2371
|
+
updatedAtMs: Date.now()
|
|
2319
2372
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
...messageEvent,
|
|
2323
|
-
...message.meta?.replied === true ? [
|
|
2324
|
-
{
|
|
2325
|
-
idempotencyKey: `message:${stored.messageId}:handled`,
|
|
2326
|
-
createdAtMs: handledAtMs,
|
|
2327
|
-
data: {
|
|
2328
|
-
type: "message_handled",
|
|
2329
|
-
messageId: stored.messageId
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
] : []
|
|
2333
|
-
];
|
|
2334
|
-
});
|
|
2335
|
-
await getConversationEventStore().append(args.conversationId, events);
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2336
2375
|
}
|
|
2337
2376
|
|
|
2338
2377
|
// src/chat/state/artifacts.ts
|
|
@@ -2793,11 +2832,29 @@ export {
|
|
|
2793
2832
|
stripRuntimeTurnContext,
|
|
2794
2833
|
historyItemFromPiMessage,
|
|
2795
2834
|
piMessageFromHistoryItem,
|
|
2835
|
+
persistConversationMessageSummaries,
|
|
2836
|
+
estimateTextTokens,
|
|
2837
|
+
getAgentContextCompactionTriggerTokens,
|
|
2838
|
+
getAgentContextInputLimitTokens,
|
|
2839
|
+
normalizeConversationText,
|
|
2840
|
+
updateConversationStats,
|
|
2841
|
+
upsertConversationMessage,
|
|
2842
|
+
recordDeliveredAssistantMessage,
|
|
2843
|
+
turnHasReply,
|
|
2844
|
+
markConversationMessage,
|
|
2845
|
+
buildConversationContext,
|
|
2846
|
+
getThreadTitleSourceMessage,
|
|
2847
|
+
createConversationMemoryService,
|
|
2848
|
+
isHumanConversationMessage,
|
|
2849
|
+
getConversationMessageSlackTs,
|
|
2850
|
+
hydrateConversationMessages,
|
|
2851
|
+
persistConversationMessages,
|
|
2796
2852
|
loadProjection,
|
|
2797
2853
|
loadConversationProjection,
|
|
2798
2854
|
openConversationProjection,
|
|
2799
2855
|
loadConnectedMcpProviders,
|
|
2800
2856
|
commitMessages,
|
|
2857
|
+
commitAcceptedReply,
|
|
2801
2858
|
recordMcpProviderConnected,
|
|
2802
2859
|
recordAuthorizationRequested,
|
|
2803
2860
|
recordAuthorizationCompleted,
|
|
@@ -2818,6 +2875,12 @@ export {
|
|
|
2818
2875
|
markTurnClosed,
|
|
2819
2876
|
markTurnCompleted,
|
|
2820
2877
|
markTurnFailed,
|
|
2878
|
+
NO_REPLY_MARKER,
|
|
2879
|
+
isNoReplyMarker,
|
|
2880
|
+
containsNoReplyMarker,
|
|
2881
|
+
sanitizeAssistantText,
|
|
2882
|
+
decideReply,
|
|
2883
|
+
getAssistantReplyText,
|
|
2821
2884
|
getAgentTurnSessionRecord,
|
|
2822
2885
|
getAgentTurnSessionRecordForResume,
|
|
2823
2886
|
recordAgentTurnSessionSummary,
|
|
@@ -2841,23 +2904,6 @@ export {
|
|
|
2841
2904
|
abandonReplacedPendingAuth,
|
|
2842
2905
|
isPendingAuthLatestRequest,
|
|
2843
2906
|
coerceThreadConversationState,
|
|
2844
|
-
persistConversationMessageSummaries,
|
|
2845
|
-
estimateTextTokens,
|
|
2846
|
-
getAgentContextCompactionTriggerTokens,
|
|
2847
|
-
getAgentContextInputLimitTokens,
|
|
2848
|
-
normalizeConversationText,
|
|
2849
|
-
updateConversationStats,
|
|
2850
|
-
upsertConversationMessage,
|
|
2851
|
-
recordDeliveredAssistantMessage,
|
|
2852
|
-
turnHasReply,
|
|
2853
|
-
markConversationMessage,
|
|
2854
|
-
buildConversationContext,
|
|
2855
|
-
getThreadTitleSourceMessage,
|
|
2856
|
-
createConversationMemoryService,
|
|
2857
|
-
isHumanConversationMessage,
|
|
2858
|
-
getConversationMessageSlackTs,
|
|
2859
|
-
hydrateConversationMessages,
|
|
2860
|
-
persistConversationMessages,
|
|
2861
2907
|
coerceThreadArtifactsState,
|
|
2862
2908
|
mergeArtifactsState,
|
|
2863
2909
|
getPersistedSandboxState,
|