@sentry/junior 0.105.0 → 0.107.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-EOFSY2PD.js → agent-hooks-ICPIJAFY.js} +9 -7
- package/dist/api/conversations/detail.d.ts +1 -1
- package/dist/api/conversations/events.d.ts +14 -0
- package/dist/api/conversations/schema.d.ts +196 -463
- package/dist/api/schema.d.ts +2 -7
- package/dist/api/schema.js +10 -8
- package/dist/api.js +377 -810
- package/dist/app.js +2284 -2288
- package/dist/{catalog-runtime-5CVM2DOF.js → catalog-runtime-FOFNPR3P.js} +3 -2
- package/dist/chat/agent/prompt.d.ts +1 -1
- package/dist/chat/agent/request.d.ts +28 -29
- package/dist/chat/agent/resume.d.ts +7 -7
- package/dist/chat/agent/session.d.ts +5 -6
- package/dist/chat/agent/tools.d.ts +3 -3
- package/dist/chat/agent-dispatch/runner.d.ts +2 -0
- package/dist/chat/config.d.ts +3 -2
- package/dist/chat/conversations/history.d.ts +582 -167
- package/dist/chat/conversations/message-projection.d.ts +6 -0
- package/dist/chat/conversations/message-summaries.d.ts +9 -0
- package/dist/chat/conversations/messages.d.ts +13 -27
- package/dist/chat/conversations/projection.d.ts +30 -53
- package/dist/chat/conversations/provenance.d.ts +42 -0
- package/dist/chat/conversations/search.d.ts +2 -2
- package/dist/chat/conversations/sql/conversation-row.d.ts +1 -1
- package/dist/chat/conversations/sql/event-lock.d.ts +3 -0
- package/dist/chat/conversations/sql/history.d.ts +3 -3
- package/dist/chat/conversations/sql/privacy.d.ts +27 -0
- package/dist/chat/conversations/sql/purge.d.ts +2 -10
- package/dist/chat/conversations/sql/store.d.ts +0 -5
- package/dist/chat/conversations/store.d.ts +8 -13
- package/dist/chat/conversations/turn-lifecycle.d.ts +41 -0
- package/dist/chat/db.d.ts +4 -10
- package/dist/chat/model-profile.d.ts +10 -0
- package/dist/chat/oauth-flow.d.ts +0 -5
- package/dist/chat/pi/conversation-events.d.ts +33 -0
- package/dist/chat/pi/sql-model-usage.d.ts +10 -0
- package/dist/chat/plugins/agent-hooks.d.ts +4 -2
- package/dist/chat/resource-events/ingest.d.ts +0 -13
- package/dist/chat/runtime/delivered-turn-state.d.ts +1 -1
- package/dist/chat/runtime/generated-artifacts.d.ts +1 -1
- package/dist/chat/runtime/reply-executor.d.ts +2 -0
- package/dist/chat/runtime/slack-resume.d.ts +23 -28
- package/dist/chat/runtime/slack-runtime.d.ts +2 -0
- package/dist/chat/runtime/turn.d.ts +14 -1
- package/dist/chat/services/context-compaction-marker.d.ts +0 -1
- package/dist/chat/services/context-compaction.d.ts +1 -0
- package/dist/chat/services/conversation-memory.d.ts +9 -0
- package/dist/chat/services/plugin-auth-orchestration.d.ts +1 -4
- package/dist/chat/services/subscribed-decision.d.ts +1 -1
- package/dist/chat/services/turn-failure-response.d.ts +10 -3
- package/dist/chat/services/turn-limit.d.ts +6 -0
- package/dist/chat/services/turn-result.d.ts +7 -12
- package/dist/chat/services/{turn-reasoning-level.d.ts → turn-router.d.ts} +12 -8
- package/dist/chat/services/turn-session-record.d.ts +15 -17
- package/dist/chat/slack/adapter.d.ts +2 -2
- package/dist/chat/slack/errors.d.ts +2 -0
- package/dist/chat/slack/outbound.d.ts +0 -1
- package/dist/chat/slack/reply.d.ts +2 -8
- package/dist/chat/slack/tools/channel-capabilities.d.ts +2 -2
- package/dist/chat/slack/tools/{send-message.d.ts → send-files.d.ts} +27 -19
- package/dist/chat/state/conversation.d.ts +4 -4
- package/dist/chat/state/turn-id.d.ts +2 -0
- package/dist/chat/state/turn-session-keys.d.ts +6 -0
- package/dist/chat/state/turn-session.d.ts +11 -9
- package/dist/chat/task-execution/state.d.ts +6 -1
- package/dist/chat/task-execution/store.d.ts +1 -1
- package/dist/chat/tool-support/pi-tool-adapter.d.ts +1 -1
- package/dist/chat/tool-support/tool-execution-report.d.ts +1 -0
- package/dist/chat/tools/handoff/tool.d.ts +2 -2
- package/dist/chat/tools/sandbox/file-uploads.d.ts +28 -6
- package/dist/chat/tools/types.d.ts +6 -9
- package/dist/chat/tools/web/image-generate.d.ts +4 -6
- package/dist/{chunk-QDKRE3LS.js → chunk-4YF7Z6IA.js} +390 -270
- package/dist/{chunk-4PDVOHXP.js → chunk-AUUOHQAT.js} +2 -2
- package/dist/chunk-B2Z2H66D.js +250 -0
- package/dist/{chunk-QVTVCJR4.js → chunk-CQ7KSO2B.js} +75 -45
- package/dist/{chunk-MJSJAHBW.js → chunk-DY7TRYB5.js} +1 -1
- package/dist/{chunk-NJJFU6CP.js → chunk-EDLNHZH3.js} +883 -898
- package/dist/{chunk-O7ILGCMJ.js → chunk-IGHMVDWI.js} +134 -18
- package/dist/chunk-MU6HHZEN.js +99 -0
- package/dist/{chunk-BCULHGZK.js → chunk-NVOTGWYX.js} +458 -684
- package/dist/{chunk-AEWZ7F65.js → chunk-O24R4DCK.js} +2 -179
- package/dist/{chunk-CCFROYAV.js → chunk-PUAMO4PI.js} +115 -125
- package/dist/{chunk-VDHMFCDY.js → chunk-RMVOAJRL.js} +30 -13
- package/dist/{chunk-7G4J3CR7.js → chunk-SPUAJVVH.js} +6 -4
- package/dist/{chunk-M3UAVLEN.js → chunk-SS67LUOK.js} +43 -290
- package/dist/{chunk-FXFQNHRU.js → chunk-VFUK3X5B.js} +4 -2
- package/dist/{chunk-CCTCIISK.js → chunk-VH6KWKG2.js} +2 -2
- package/dist/chunk-VIXFG2VC.js +182 -0
- package/dist/{chunk-RMZ65YAA.js → chunk-YFQ7CQDE.js} +687 -21
- package/dist/{chunk-77LHNQK4.js → chunk-YIPD3L6L.js} +4 -2
- package/dist/{chunk-U26ZWZCN.js → chunk-YNP2ATQX.js} +6 -4
- package/dist/{chunk-WR7H7VLJ.js → chunk-YT72TOEK.js} +2 -2
- package/dist/cli/chat.js +21 -20
- package/dist/cli/check.js +4 -3
- package/dist/cli/plugins.js +11 -9
- package/dist/cli/snapshot-warmup.js +7 -5
- package/dist/cli/upgrade.js +2046 -105
- package/dist/db/schema/{agent-steps.d.ts → conversation-events.d.ts} +37 -21
- package/dist/db/schema.d.ts +28 -159
- package/dist/{db-PDMC67T3.js → db-DIGO4TGW.js} +6 -8
- package/dist/{runner-6A7MQRUE.js → runner-ACR2HAIC.js} +209 -101
- package/dist/{skills-722AXRV5.js → skills-ITNHBARB.js} +4 -3
- package/dist/{validation-EUI2UFRM.js → validation-AGQ46ZGV.js} +4 -3
- package/migrations/0005_conversation_events.sql +122 -0
- package/migrations/README.md +23 -10
- package/migrations/meta/0005_snapshot.json +948 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +6 -6
- package/dist/api/conversations/activity.d.ts +0 -40
- package/dist/api/conversations/detail-projection.d.ts +0 -11
- package/dist/api/conversations/detail.query.d.ts +0 -3
- package/dist/api/conversations/subagent.d.ts +0 -10
- package/dist/api/conversations/transcript.d.ts +0 -28
- package/dist/chat/conversations/legacy-advisor-session.d.ts +0 -7
- package/dist/chat/conversations/legacy-import.d.ts +0 -38
- package/dist/chat/conversations/sql/legacy-history-import.d.ts +0 -55
- package/dist/chat/conversations/sql/messages.d.ts +0 -4
- package/dist/chat/conversations/visible-compactions.d.ts +0 -11
- package/dist/chat/conversations/visible-messages.d.ts +0 -47
- package/dist/chat/services/reply-delivery-plan.d.ts +0 -5
- package/dist/chat/state/session-log.d.ts +0 -384
- package/dist/chunk-RYDF4GOX.js +0 -1072
- package/dist/chunk-XIMUQ4IU.js +0 -147
- package/dist/db/schema/conversation-messages.d.ts +0 -153
- package/dist/handlers/github-webhook/check-suite.d.ts +0 -3
- package/dist/handlers/github-webhook/issue-comment.d.ts +0 -3
- package/dist/handlers/github-webhook/pull-request-review-comment.d.ts +0 -3
- package/dist/handlers/github-webhook/pull-request-review.d.ts +0 -3
- package/dist/handlers/github-webhook/pull-request.d.ts +0 -3
- package/dist/handlers/github-webhook/resource.d.ts +0 -10
- package/dist/handlers/github-webhook.d.ts +0 -15
- package/dist/legacy-import-G333RHJZ.js +0 -18
|
@@ -1,40 +1,73 @@
|
|
|
1
1
|
import {
|
|
2
2
|
JUNIOR_THREAD_STATE_TTL_MS,
|
|
3
|
-
buildConversationStatePatch
|
|
4
|
-
} from "./chunk-XIMUQ4IU.js";
|
|
5
|
-
import {
|
|
6
3
|
abandonAgentTurnSessionRecord,
|
|
7
|
-
|
|
4
|
+
buildConversationStatePatch,
|
|
8
5
|
getAgentTurnSessionRecord,
|
|
6
|
+
getAgentTurnSessionRecordForResume,
|
|
9
7
|
upsertAgentTurnSessionRecord
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4YF7Z6IA.js";
|
|
9
|
+
import {
|
|
10
|
+
addAgentTurnUsage
|
|
11
|
+
} from "./chunk-MU6HHZEN.js";
|
|
12
|
+
import {
|
|
13
|
+
getStateAdapter
|
|
14
|
+
} from "./chunk-B2Z2H66D.js";
|
|
11
15
|
import {
|
|
12
|
-
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
getConversationEventStore
|
|
17
|
+
} from "./chunk-NVOTGWYX.js";
|
|
14
18
|
import {
|
|
15
19
|
sleep
|
|
16
20
|
} from "./chunk-4ZNGQH7C.js";
|
|
17
21
|
import {
|
|
22
|
+
botConfig,
|
|
23
|
+
escapeXml,
|
|
18
24
|
getPiMessageRole,
|
|
19
|
-
|
|
25
|
+
resolveGatewayModel,
|
|
26
|
+
standardModelId,
|
|
20
27
|
trimTrailingAssistantMessages
|
|
21
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-SS67LUOK.js";
|
|
22
29
|
import {
|
|
23
30
|
getActiveTraceId,
|
|
24
|
-
logException
|
|
25
|
-
|
|
31
|
+
logException,
|
|
32
|
+
logWarn,
|
|
33
|
+
setSpanAttributes
|
|
34
|
+
} from "./chunk-O24R4DCK.js";
|
|
26
35
|
import {
|
|
27
36
|
isRecord,
|
|
28
37
|
toOptionalString
|
|
29
38
|
} from "./chunk-OB42YVAE.js";
|
|
39
|
+
import {
|
|
40
|
+
parseSlackMessageTs
|
|
41
|
+
} from "./chunk-7FBGKXPE.js";
|
|
30
42
|
|
|
31
43
|
// src/chat/state/turn-id.ts
|
|
32
44
|
function buildDeterministicTurnId(messageId) {
|
|
33
45
|
const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
34
46
|
return `turn_${sanitized}`;
|
|
35
47
|
}
|
|
48
|
+
function buildDeterministicAssistantMessageId(turnId) {
|
|
49
|
+
return `${turnId}:assistant:1`;
|
|
50
|
+
}
|
|
36
51
|
|
|
37
52
|
// src/chat/runtime/turn.ts
|
|
53
|
+
var ConversationTurnBoundaryError = class extends Error {
|
|
54
|
+
eventId;
|
|
55
|
+
failureCode;
|
|
56
|
+
constructor(args) {
|
|
57
|
+
super(
|
|
58
|
+
args.cause instanceof Error ? args.cause.message : String(args.cause),
|
|
59
|
+
{
|
|
60
|
+
cause: args.cause
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
this.name = "ConversationTurnBoundaryError";
|
|
64
|
+
this.eventId = args.eventId;
|
|
65
|
+
this.failureCode = args.failureCode;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function getConversationTurnBoundaryError(error) {
|
|
69
|
+
return error instanceof ConversationTurnBoundaryError ? error : void 0;
|
|
70
|
+
}
|
|
38
71
|
var CooperativeTurnYieldError = class extends Error {
|
|
39
72
|
code = "cooperative_turn_yield";
|
|
40
73
|
constructor(message = "Agent turn yielded at a safe boundary") {
|
|
@@ -112,8 +145,18 @@ async function persistWithRetry(persist) {
|
|
|
112
145
|
throw lastError;
|
|
113
146
|
}
|
|
114
147
|
|
|
148
|
+
// src/chat/services/turn-limit.ts
|
|
149
|
+
var TurnSliceLimitExceededError = class extends Error {
|
|
150
|
+
constructor(maxSlices) {
|
|
151
|
+
super(`Agent turn exceeded execution limit (${maxSlices} slices)`);
|
|
152
|
+
this.name = "TurnSliceLimitExceededError";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
function buildTurnLimitResponse(eventId) {
|
|
156
|
+
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}\`.`;
|
|
157
|
+
}
|
|
158
|
+
|
|
115
159
|
// src/chat/services/turn-session-record.ts
|
|
116
|
-
var AGENT_CONTINUE_MAX_SLICES = 48;
|
|
117
160
|
function logSessionRecordError(error, eventName, args, attributes, message) {
|
|
118
161
|
logException(
|
|
119
162
|
error,
|
|
@@ -155,13 +198,14 @@ function resumableBoundary(messages, fallbackMessages) {
|
|
|
155
198
|
return trimTrailingAssistantMessages(fallbackMessages ?? []);
|
|
156
199
|
}
|
|
157
200
|
async function loadTurnSessionRecord(ctx) {
|
|
158
|
-
const
|
|
159
|
-
|
|
201
|
+
const existingSessionRecord = await getAgentTurnSessionRecordForResume(
|
|
202
|
+
ctx.conversationId,
|
|
203
|
+
ctx.sessionId
|
|
204
|
+
);
|
|
160
205
|
const hasAwaitingResumeRecord = Boolean(
|
|
161
206
|
existingSessionRecord && existingSessionRecord.state === "awaiting_resume"
|
|
162
207
|
);
|
|
163
208
|
return {
|
|
164
|
-
canUseTurnSession,
|
|
165
209
|
resumedFromSessionRecord: hasAwaitingResumeRecord,
|
|
166
210
|
currentSliceId: hasAwaitingResumeRecord ? existingSessionRecord.sliceId : 1,
|
|
167
211
|
existingSessionRecord
|
|
@@ -340,7 +384,7 @@ async function persistAuthPauseSessionRecord(args) {
|
|
|
340
384
|
}
|
|
341
385
|
return void 0;
|
|
342
386
|
}
|
|
343
|
-
async function
|
|
387
|
+
async function persistContinuationSessionRecord(args) {
|
|
344
388
|
const nextSliceId = args.currentSliceId + 1;
|
|
345
389
|
try {
|
|
346
390
|
const latestSessionRecord = await getAgentTurnSessionRecord(
|
|
@@ -362,7 +406,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
362
406
|
latestSessionRecord?.cumulativeUsage,
|
|
363
407
|
args.currentUsage
|
|
364
408
|
);
|
|
365
|
-
if (nextSliceId >
|
|
409
|
+
if (nextSliceId > botConfig.maxSlicesPerTurn) {
|
|
366
410
|
return await upsertAgentTurnSessionRecord({
|
|
367
411
|
...args.channelName ?? latestSessionRecord?.channelName ? {
|
|
368
412
|
channelName: args.channelName ?? latestSessionRecord?.channelName
|
|
@@ -384,9 +428,11 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
384
428
|
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
385
429
|
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
386
430
|
} : {},
|
|
387
|
-
resumeReason:
|
|
431
|
+
resumeReason: args.resumeReason,
|
|
388
432
|
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
389
|
-
errorMessage:
|
|
433
|
+
errorMessage: new TurnSliceLimitExceededError(
|
|
434
|
+
botConfig.maxSlicesPerTurn
|
|
435
|
+
).message,
|
|
390
436
|
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
391
437
|
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
392
438
|
});
|
|
@@ -408,7 +454,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
408
454
|
...args.reasoningLevel ?? latestSessionRecord?.reasoningLevel ? {
|
|
409
455
|
reasoningLevel: args.reasoningLevel ?? latestSessionRecord?.reasoningLevel
|
|
410
456
|
} : {},
|
|
411
|
-
resumeReason:
|
|
457
|
+
resumeReason: args.resumeReason,
|
|
412
458
|
resumedFromSliceId: args.currentSliceId,
|
|
413
459
|
errorMessage: args.errorMessage,
|
|
414
460
|
...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
|
|
@@ -484,6 +530,605 @@ async function persistYieldSessionRecord(args) {
|
|
|
484
530
|
}
|
|
485
531
|
}
|
|
486
532
|
|
|
533
|
+
// src/chat/services/context-budget.ts
|
|
534
|
+
var COMPACTION_TRIGGER_INPUT_RATIO = 0.75;
|
|
535
|
+
var COMPACTION_OUTPUT_RESERVE_RATIO = 0.25;
|
|
536
|
+
var COMPACTION_TARGET_RATIO = 0.8;
|
|
537
|
+
var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
|
|
538
|
+
var FALLBACK_MAX_OUTPUT_TOKENS = 128e3;
|
|
539
|
+
function positiveInteger(value, fallback) {
|
|
540
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
541
|
+
}
|
|
542
|
+
function estimateTextTokens(text) {
|
|
543
|
+
return Math.ceil(text.length / 4);
|
|
544
|
+
}
|
|
545
|
+
function calculateContextCompactionTriggerTokens(model) {
|
|
546
|
+
const contextWindow = positiveInteger(
|
|
547
|
+
model.contextWindow,
|
|
548
|
+
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
549
|
+
);
|
|
550
|
+
const maxTokens = positiveInteger(
|
|
551
|
+
model.maxTokens,
|
|
552
|
+
FALLBACK_MAX_OUTPUT_TOKENS
|
|
553
|
+
);
|
|
554
|
+
const outputReserve = Math.min(
|
|
555
|
+
maxTokens,
|
|
556
|
+
Math.floor(contextWindow * COMPACTION_OUTPUT_RESERVE_RATIO)
|
|
557
|
+
);
|
|
558
|
+
const usableInputTokens = Math.max(1, contextWindow - outputReserve);
|
|
559
|
+
return Math.max(
|
|
560
|
+
1,
|
|
561
|
+
Math.floor(usableInputTokens * COMPACTION_TRIGGER_INPUT_RATIO)
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
function calculateContextCompactionTargetTokens(triggerTokens) {
|
|
565
|
+
return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
|
|
566
|
+
}
|
|
567
|
+
function getAgentContextCompactionTriggerTokens() {
|
|
568
|
+
const model = resolveGatewayModel(standardModelId(botConfig));
|
|
569
|
+
return calculateContextCompactionTriggerTokens({
|
|
570
|
+
contextWindow: botConfig.modelContextWindowTokens ?? model.contextWindow,
|
|
571
|
+
maxTokens: model.maxTokens
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
function getConversationContextCompactionTriggerTokens() {
|
|
575
|
+
const model = resolveGatewayModel(botConfig.fastModelId);
|
|
576
|
+
return calculateContextCompactionTriggerTokens({
|
|
577
|
+
contextWindow: model.contextWindow,
|
|
578
|
+
maxTokens: model.maxTokens
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/chat/services/conversation-memory.ts
|
|
583
|
+
var CONTEXT_MIN_LIVE_MESSAGES = 12;
|
|
584
|
+
var CONTEXT_COMPACTION_BATCH_SIZE = 24;
|
|
585
|
+
var CONTEXT_MAX_COMPACTIONS = 16;
|
|
586
|
+
var CONTEXT_MAX_MESSAGE_CHARS = 3200;
|
|
587
|
+
var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
588
|
+
function generateConversationId(prefix) {
|
|
589
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
590
|
+
}
|
|
591
|
+
function normalizeConversationText(text) {
|
|
592
|
+
return text.trim().replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS);
|
|
593
|
+
}
|
|
594
|
+
function buildImageContextSuffix(message, conversation) {
|
|
595
|
+
const byFileId = conversation?.vision.byFileId;
|
|
596
|
+
const imageFileIds = message.meta?.imageFileIds ?? [];
|
|
597
|
+
if (!byFileId || imageFileIds.length === 0) {
|
|
598
|
+
return "";
|
|
599
|
+
}
|
|
600
|
+
const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
|
|
601
|
+
if (summaries.length === 0) {
|
|
602
|
+
return "";
|
|
603
|
+
}
|
|
604
|
+
return ` [image context: ${summaries.join(" | ")}]`;
|
|
605
|
+
}
|
|
606
|
+
function renderConversationMessageLine(message, conversation) {
|
|
607
|
+
const displayName = conversationAuthorDisplayName(message);
|
|
608
|
+
const markers = [];
|
|
609
|
+
if (message.meta?.replied === false) {
|
|
610
|
+
markers.push(
|
|
611
|
+
`assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (message.meta?.explicitMention) {
|
|
615
|
+
markers.push("explicit mention");
|
|
616
|
+
}
|
|
617
|
+
const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
|
|
618
|
+
const imageContext = buildImageContextSuffix(message, conversation);
|
|
619
|
+
return `[${message.role}] ${displayName}: ${message.text}${imageContext}${markerSuffix}`;
|
|
620
|
+
}
|
|
621
|
+
function conversationAuthorDisplayName(message) {
|
|
622
|
+
const author = message.author;
|
|
623
|
+
const fullName = authorDisplayField(author?.fullName, author?.userId);
|
|
624
|
+
const userName = authorDisplayField(author?.userName, author?.userId);
|
|
625
|
+
return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
|
|
626
|
+
}
|
|
627
|
+
function authorDisplayField(value, userId) {
|
|
628
|
+
if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
|
|
629
|
+
return void 0;
|
|
630
|
+
}
|
|
631
|
+
return value;
|
|
632
|
+
}
|
|
633
|
+
function updateConversationStats(conversation) {
|
|
634
|
+
const contextText = buildConversationContext(conversation);
|
|
635
|
+
conversation.stats.estimatedContextTokens = estimateTextTokens(
|
|
636
|
+
contextText ?? ""
|
|
637
|
+
);
|
|
638
|
+
conversation.stats.totalMessageCount = conversation.messages.length;
|
|
639
|
+
conversation.stats.updatedAtMs = Date.now();
|
|
640
|
+
}
|
|
641
|
+
function upsertConversationMessage(conversation, message) {
|
|
642
|
+
const existingIndex = conversation.messages.findIndex(
|
|
643
|
+
(entry) => entry.id === message.id
|
|
644
|
+
);
|
|
645
|
+
if (existingIndex >= 0) {
|
|
646
|
+
conversation.messages[existingIndex] = {
|
|
647
|
+
...conversation.messages[existingIndex],
|
|
648
|
+
...message,
|
|
649
|
+
meta: {
|
|
650
|
+
...conversation.messages[existingIndex]?.meta,
|
|
651
|
+
...message.meta
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
updateConversationStats(conversation);
|
|
655
|
+
return message.id;
|
|
656
|
+
}
|
|
657
|
+
conversation.messages.push(message);
|
|
658
|
+
updateConversationStats(conversation);
|
|
659
|
+
return message.id;
|
|
660
|
+
}
|
|
661
|
+
function recordDeliveredAssistantMessage(args) {
|
|
662
|
+
const prefix = `${args.sessionId}:assistant:`;
|
|
663
|
+
const ordinal = args.conversation.messages.filter(
|
|
664
|
+
(message) => message.id.startsWith(prefix)
|
|
665
|
+
).length + 1;
|
|
666
|
+
const messageId = `${prefix}${ordinal}`;
|
|
667
|
+
markConversationMessage(args.conversation, args.userMessageId, {
|
|
668
|
+
replied: true,
|
|
669
|
+
skippedReason: void 0
|
|
670
|
+
});
|
|
671
|
+
upsertConversationMessage(args.conversation, {
|
|
672
|
+
id: messageId,
|
|
673
|
+
role: "assistant",
|
|
674
|
+
text: normalizeConversationText(args.text),
|
|
675
|
+
createdAtMs: Date.now(),
|
|
676
|
+
author: {
|
|
677
|
+
userName: botConfig.userName,
|
|
678
|
+
isBot: true
|
|
679
|
+
},
|
|
680
|
+
meta: {
|
|
681
|
+
replied: true
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
return messageId;
|
|
685
|
+
}
|
|
686
|
+
function turnHasReply(conversation, turnId) {
|
|
687
|
+
const assistantPrefix = `${turnId}:assistant:`;
|
|
688
|
+
return conversation.messages.some(
|
|
689
|
+
(message) => message.role === "assistant" && message.id.startsWith(assistantPrefix)
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
function markConversationMessage(conversation, messageId, patch) {
|
|
693
|
+
if (!messageId) return;
|
|
694
|
+
const messageIndex = conversation.messages.findIndex(
|
|
695
|
+
(entry) => entry.id === messageId
|
|
696
|
+
);
|
|
697
|
+
if (messageIndex < 0) return;
|
|
698
|
+
const current = conversation.messages[messageIndex];
|
|
699
|
+
conversation.messages[messageIndex] = {
|
|
700
|
+
...current,
|
|
701
|
+
meta: {
|
|
702
|
+
...current.meta ?? {},
|
|
703
|
+
...patch
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
updateConversationStats(conversation);
|
|
707
|
+
}
|
|
708
|
+
function buildConversationContext(conversation, options = {}) {
|
|
709
|
+
const messages = conversation.messages.filter(
|
|
710
|
+
(entry) => entry.id !== options.excludeMessageId
|
|
711
|
+
);
|
|
712
|
+
if (messages.length === 0 && conversation.compactions.length === 0) {
|
|
713
|
+
return void 0;
|
|
714
|
+
}
|
|
715
|
+
const lines = [];
|
|
716
|
+
if (conversation.compactions.length > 0) {
|
|
717
|
+
lines.push("<thread-compactions>");
|
|
718
|
+
for (const [index, compaction] of conversation.compactions.entries()) {
|
|
719
|
+
lines.push(
|
|
720
|
+
` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageCount}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
|
|
721
|
+
compaction.summary,
|
|
722
|
+
" </compaction>"
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
lines.push("</thread-compactions>");
|
|
726
|
+
}
|
|
727
|
+
if (messages.length > 0) {
|
|
728
|
+
if (lines.length > 0) {
|
|
729
|
+
lines.push("");
|
|
730
|
+
}
|
|
731
|
+
lines.push("<thread-transcript>");
|
|
732
|
+
for (const [index, message] of messages.entries()) {
|
|
733
|
+
const author = escapeXml(conversationAuthorDisplayName(message));
|
|
734
|
+
const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
|
|
735
|
+
const ts = new Date(message.createdAtMs).toISOString();
|
|
736
|
+
const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
|
|
737
|
+
lines.push(
|
|
738
|
+
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
|
|
739
|
+
renderConversationMessageLine(message, conversation),
|
|
740
|
+
" </message>"
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
lines.push("</thread-transcript>");
|
|
744
|
+
}
|
|
745
|
+
return lines.join("\n");
|
|
746
|
+
}
|
|
747
|
+
function pruneCompactions(compactions) {
|
|
748
|
+
if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
|
|
749
|
+
return compactions;
|
|
750
|
+
}
|
|
751
|
+
const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
|
|
752
|
+
const merged = compactions.slice(0, overflowCount);
|
|
753
|
+
const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
|
|
754
|
+
const coveredMessageCount = merged.reduce(
|
|
755
|
+
(count, entry) => count + entry.coveredMessageCount,
|
|
756
|
+
0
|
|
757
|
+
);
|
|
758
|
+
const compacted = {
|
|
759
|
+
id: generateConversationId("compaction"),
|
|
760
|
+
createdAtMs: Date.now(),
|
|
761
|
+
summary: mergedSummary,
|
|
762
|
+
coveredMessageCount
|
|
763
|
+
};
|
|
764
|
+
return [compacted, ...compactions.slice(overflowCount)];
|
|
765
|
+
}
|
|
766
|
+
async function summarizeConversationChunk(messages, conversation, context, deps) {
|
|
767
|
+
const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
|
|
768
|
+
try {
|
|
769
|
+
const result = await deps.completeText({
|
|
770
|
+
modelId: botConfig.fastModelId,
|
|
771
|
+
temperature: 0,
|
|
772
|
+
messages: [
|
|
773
|
+
{
|
|
774
|
+
role: "user",
|
|
775
|
+
content: [
|
|
776
|
+
"Summarize the following older Slack thread transcript segment for future assistant turns.",
|
|
777
|
+
"Keep the summary factual and concise. Do not invent details.",
|
|
778
|
+
"",
|
|
779
|
+
"Output exactly three XML sections in this order:",
|
|
780
|
+
"<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>",
|
|
781
|
+
"<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>",
|
|
782
|
+
"<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>",
|
|
783
|
+
"",
|
|
784
|
+
"Do not output any text outside the three sections.",
|
|
785
|
+
"",
|
|
786
|
+
transcript
|
|
787
|
+
].join("\n"),
|
|
788
|
+
timestamp: Date.now()
|
|
789
|
+
}
|
|
790
|
+
],
|
|
791
|
+
metadata: {
|
|
792
|
+
modelId: botConfig.fastModelId,
|
|
793
|
+
threadId: context.threadId ?? "",
|
|
794
|
+
channelId: context.channelId ?? "",
|
|
795
|
+
actorId: context.actorId ?? "",
|
|
796
|
+
runId: context.runId ?? ""
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
const summary = result.text.trim();
|
|
800
|
+
if (summary.length > 0) {
|
|
801
|
+
return summary.slice(0, 3500);
|
|
802
|
+
}
|
|
803
|
+
} catch (error) {
|
|
804
|
+
logWarn(
|
|
805
|
+
"conversation_compaction_summary_failed",
|
|
806
|
+
{
|
|
807
|
+
slackThreadId: context.threadId,
|
|
808
|
+
slackUserId: context.actorId,
|
|
809
|
+
slackChannelId: context.channelId,
|
|
810
|
+
runId: context.runId,
|
|
811
|
+
assistantUserName: botConfig.userName,
|
|
812
|
+
modelId: botConfig.fastModelId
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
816
|
+
"app.compaction_messages_covered": messages.length
|
|
817
|
+
},
|
|
818
|
+
"Compaction summarization failed; using fallback summary"
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
return transcript.slice(0, 2800);
|
|
822
|
+
}
|
|
823
|
+
async function generateThreadTitleWithDeps(sourceText, deps) {
|
|
824
|
+
const result = await deps.completeText({
|
|
825
|
+
modelId: botConfig.fastModelId,
|
|
826
|
+
temperature: 0,
|
|
827
|
+
messages: [
|
|
828
|
+
{
|
|
829
|
+
role: "user",
|
|
830
|
+
content: [
|
|
831
|
+
"Generate a concise 5-8 word Slack conversation title from the first user message below.",
|
|
832
|
+
"Capture the user's main request.",
|
|
833
|
+
"Reply with ONLY the title, with no quotes or trailing punctuation.",
|
|
834
|
+
"",
|
|
835
|
+
`First user message: ${sourceText.slice(0, 500)}`
|
|
836
|
+
].join("\n"),
|
|
837
|
+
timestamp: Date.now()
|
|
838
|
+
}
|
|
839
|
+
],
|
|
840
|
+
metadata: {
|
|
841
|
+
modelId: botConfig.fastModelId
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
return result.text.trim().slice(0, 60);
|
|
845
|
+
}
|
|
846
|
+
function getThreadTitleSourceMessage(conversation) {
|
|
847
|
+
let firstMessage;
|
|
848
|
+
for (const message of conversation.messages) {
|
|
849
|
+
if (!isHumanConversationMessage(message)) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (!firstMessage) {
|
|
853
|
+
firstMessage = message;
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (message.createdAtMs < firstMessage.createdAtMs) {
|
|
857
|
+
firstMessage = message;
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
|
|
861
|
+
firstMessage = message;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return firstMessage;
|
|
865
|
+
}
|
|
866
|
+
async function compactConversationIfNeededWithDeps(conversation, context, deps) {
|
|
867
|
+
updateConversationStats(conversation);
|
|
868
|
+
let estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
869
|
+
setSpanAttributes({
|
|
870
|
+
"app.context_tokens_estimated": estimatedTokens
|
|
871
|
+
});
|
|
872
|
+
const triggerTokens = getConversationContextCompactionTriggerTokens();
|
|
873
|
+
const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
|
|
874
|
+
while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
|
|
875
|
+
const compactCount = Math.min(
|
|
876
|
+
CONTEXT_COMPACTION_BATCH_SIZE,
|
|
877
|
+
conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
|
|
878
|
+
);
|
|
879
|
+
if (compactCount <= 0) {
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
const compactedChunk = conversation.messages.slice(0, compactCount);
|
|
883
|
+
const summary = await summarizeConversationChunk(
|
|
884
|
+
compactedChunk,
|
|
885
|
+
conversation,
|
|
886
|
+
context,
|
|
887
|
+
deps
|
|
888
|
+
);
|
|
889
|
+
conversation.compactions.push({
|
|
890
|
+
id: generateConversationId("compaction"),
|
|
891
|
+
createdAtMs: Date.now(),
|
|
892
|
+
summary,
|
|
893
|
+
coveredMessageCount: compactedChunk.length
|
|
894
|
+
});
|
|
895
|
+
conversation.compactions = pruneCompactions(conversation.compactions);
|
|
896
|
+
conversation.messages = conversation.messages.slice(compactCount);
|
|
897
|
+
conversation.stats.compactedMessageCount += compactCount;
|
|
898
|
+
updateConversationStats(conversation);
|
|
899
|
+
estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
900
|
+
setSpanAttributes({
|
|
901
|
+
"app.compaction_messages_covered": compactCount,
|
|
902
|
+
"app.compaction.trigger_tokens": triggerTokens,
|
|
903
|
+
"app.compaction.target_tokens": targetTokens,
|
|
904
|
+
"app.context_tokens_estimated": estimatedTokens
|
|
905
|
+
});
|
|
906
|
+
if (estimatedTokens <= targetTokens) {
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function createConversationMemoryService(deps) {
|
|
912
|
+
return {
|
|
913
|
+
compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps),
|
|
914
|
+
generateThreadTitle: async (sourceText) => await generateThreadTitleWithDeps(sourceText, deps)
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function isHumanConversationMessage(message) {
|
|
918
|
+
return message.role === "user" && message.author?.isBot !== true;
|
|
919
|
+
}
|
|
920
|
+
function getConversationMessageSlackTs(message) {
|
|
921
|
+
return parseSlackMessageTs(message.meta?.slackTs);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/chat/conversations/messages.ts
|
|
925
|
+
import { createHash } from "crypto";
|
|
926
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
927
|
+
|
|
928
|
+
// src/chat/conversations/message-summaries.ts
|
|
929
|
+
import { isDeepStrictEqual } from "util";
|
|
930
|
+
function projectConversationMessageSummaries(events) {
|
|
931
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
932
|
+
const data = events[index]?.data;
|
|
933
|
+
if (data?.type === "messages_summarized") {
|
|
934
|
+
return data.compactions;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return [];
|
|
938
|
+
}
|
|
939
|
+
async function persistConversationMessageSummaries(args) {
|
|
940
|
+
const eventStore = getConversationEventStore();
|
|
941
|
+
const history = await eventStore.loadMessageHistory(args.conversationId);
|
|
942
|
+
const existing = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
|
|
943
|
+
if (isDeepStrictEqual(existing, args.conversation.compactions)) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const liveMessageIds = new Set(
|
|
947
|
+
args.conversation.messages.map((message) => message.id)
|
|
948
|
+
);
|
|
949
|
+
const historyFromSeq = history.events.find(
|
|
950
|
+
(event) => event.data.type === "message" && liveMessageIds.has(event.data.messageId)
|
|
951
|
+
)?.seq;
|
|
952
|
+
const nextHistoryFromSeq = historyFromSeq ?? Math.max(
|
|
953
|
+
history.compaction?.data.type === "messages_summarized" ? history.compaction.data.historyFromSeq : 0,
|
|
954
|
+
(history.events.at(-1)?.seq ?? -1) + 1
|
|
955
|
+
);
|
|
956
|
+
await eventStore.append(args.conversationId, [
|
|
957
|
+
{
|
|
958
|
+
data: {
|
|
959
|
+
type: "messages_summarized",
|
|
960
|
+
historyFromSeq: nextHistoryFromSeq,
|
|
961
|
+
compactions: args.conversation.compactions
|
|
962
|
+
},
|
|
963
|
+
createdAtMs: args.conversation.compactions.at(-1)?.createdAtMs ?? Date.now()
|
|
964
|
+
}
|
|
965
|
+
]);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/chat/conversations/message-projection.ts
|
|
969
|
+
function isMessageEvent(event) {
|
|
970
|
+
return event.data.type === "message" || event.data.type === "message_updated" || event.data.type === "message_handled";
|
|
971
|
+
}
|
|
972
|
+
function splitMeta(meta) {
|
|
973
|
+
const rest = { ...meta ?? {} };
|
|
974
|
+
const author = rest.author;
|
|
975
|
+
delete rest.author;
|
|
976
|
+
return {
|
|
977
|
+
...author ? { author } : {},
|
|
978
|
+
...Object.keys(rest).length > 0 ? { meta: rest } : {}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function missingBaselineError(event) {
|
|
982
|
+
return new Error(
|
|
983
|
+
`Message event ${event.data.type} at seq ${event.seq} references ${event.data.messageId} before message`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
function projectConversationMessages(events, options = {}) {
|
|
987
|
+
const byId = /* @__PURE__ */ new Map();
|
|
988
|
+
for (const event of events) {
|
|
989
|
+
if (!isMessageEvent(event)) continue;
|
|
990
|
+
const data = event.data;
|
|
991
|
+
const current = byId.get(data.messageId);
|
|
992
|
+
if (data.type === "message" || data.type === "message_updated") {
|
|
993
|
+
const message = {
|
|
994
|
+
id: data.messageId,
|
|
995
|
+
role: data.role,
|
|
996
|
+
text: data.text,
|
|
997
|
+
createdAtMs: event.createdAtMs,
|
|
998
|
+
...splitMeta(data.meta)
|
|
999
|
+
};
|
|
1000
|
+
if (data.type === "message" && current)
|
|
1001
|
+
throw new Error(
|
|
1002
|
+
`Duplicate message event for ${data.messageId} at seq ${event.seq}`
|
|
1003
|
+
);
|
|
1004
|
+
if (data.type === "message_updated" && !current) {
|
|
1005
|
+
if ((options.historyFromSeq ?? 0) > 0) continue;
|
|
1006
|
+
throw missingBaselineError(event);
|
|
1007
|
+
}
|
|
1008
|
+
byId.set(data.messageId, {
|
|
1009
|
+
message: {
|
|
1010
|
+
...message,
|
|
1011
|
+
createdAtMs: current?.message.createdAtMs ?? message.createdAtMs
|
|
1012
|
+
},
|
|
1013
|
+
...current?.repliedAtMs === void 0 ? {} : { repliedAtMs: current.repliedAtMs }
|
|
1014
|
+
});
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (!current) {
|
|
1018
|
+
if ((options.historyFromSeq ?? 0) > 0) continue;
|
|
1019
|
+
throw missingBaselineError(event);
|
|
1020
|
+
}
|
|
1021
|
+
current.repliedAtMs ??= event.createdAtMs;
|
|
1022
|
+
}
|
|
1023
|
+
return [...byId.values()].map(({ message, repliedAtMs }) => {
|
|
1024
|
+
if (repliedAtMs === void 0) return message;
|
|
1025
|
+
return {
|
|
1026
|
+
...message,
|
|
1027
|
+
meta: { ...message.meta ?? {}, replied: true }
|
|
1028
|
+
};
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// src/chat/conversations/messages.ts
|
|
1033
|
+
function toStoredConversationMessage(message) {
|
|
1034
|
+
const meta = {};
|
|
1035
|
+
if (message.author) meta.author = message.author;
|
|
1036
|
+
const { replied, ...restMeta } = message.meta ?? {};
|
|
1037
|
+
Object.assign(meta, restMeta);
|
|
1038
|
+
if (replied === false) meta.replied = false;
|
|
1039
|
+
return {
|
|
1040
|
+
messageId: message.id,
|
|
1041
|
+
role: message.role,
|
|
1042
|
+
text: message.text,
|
|
1043
|
+
...Object.keys(meta).length > 0 ? { meta } : {},
|
|
1044
|
+
createdAtMs: message.createdAtMs
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function canonicalJson(value) {
|
|
1048
|
+
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
1049
|
+
if (!value || typeof value !== "object") return value;
|
|
1050
|
+
return Object.fromEntries(
|
|
1051
|
+
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson(item)])
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
function messageStateKey(message) {
|
|
1055
|
+
return createHash("sha256").update(JSON.stringify(canonicalJson(message))).digest("hex").slice(0, 32);
|
|
1056
|
+
}
|
|
1057
|
+
async function hydrateConversationMessages(args) {
|
|
1058
|
+
if (!args.conversationId) {
|
|
1059
|
+
args.conversation.messages = [];
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
const history = await getConversationEventStore().loadMessageHistory(
|
|
1063
|
+
args.conversationId
|
|
1064
|
+
);
|
|
1065
|
+
args.conversation.compactions = history.compaction ? projectConversationMessageSummaries([history.compaction]) : [];
|
|
1066
|
+
const historyFromSeq = history.compaction?.data.type === "messages_summarized" ? history.compaction.data.historyFromSeq : 0;
|
|
1067
|
+
args.conversation.messages = projectConversationMessages(history.events, {
|
|
1068
|
+
historyFromSeq
|
|
1069
|
+
});
|
|
1070
|
+
updateConversationStats(args.conversation);
|
|
1071
|
+
}
|
|
1072
|
+
async function persistConversationMessages(args) {
|
|
1073
|
+
if (!args.conversationId || args.conversation.messages.length === 0) return;
|
|
1074
|
+
const history = await getConversationEventStore().loadMessageHistory(
|
|
1075
|
+
args.conversationId
|
|
1076
|
+
);
|
|
1077
|
+
const existingMessages = new Map(
|
|
1078
|
+
projectConversationMessages(history.events).map((message) => [
|
|
1079
|
+
message.id,
|
|
1080
|
+
message
|
|
1081
|
+
])
|
|
1082
|
+
);
|
|
1083
|
+
const handledAtMs = args.repliedAtMs ?? Date.now();
|
|
1084
|
+
const events = args.conversation.messages.flatMap((message) => {
|
|
1085
|
+
const stored = toStoredConversationMessage(message);
|
|
1086
|
+
const existing = existingMessages.get(stored.messageId);
|
|
1087
|
+
const changed = existing !== void 0 && !isDeepStrictEqual2(toStoredConversationMessage(existing), stored);
|
|
1088
|
+
const messageEvent = existing ? changed ? [
|
|
1089
|
+
{
|
|
1090
|
+
idempotencyKey: `message:${stored.messageId}:update:${messageStateKey(stored)}`,
|
|
1091
|
+
createdAtMs: handledAtMs,
|
|
1092
|
+
data: {
|
|
1093
|
+
type: "message_updated",
|
|
1094
|
+
messageId: stored.messageId,
|
|
1095
|
+
role: stored.role,
|
|
1096
|
+
text: stored.text,
|
|
1097
|
+
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
|
|
1098
|
+
...stored.meta ? { meta: stored.meta } : {}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
] : [] : [
|
|
1102
|
+
{
|
|
1103
|
+
idempotencyKey: `message:${stored.messageId}`,
|
|
1104
|
+
createdAtMs: stored.createdAtMs,
|
|
1105
|
+
data: {
|
|
1106
|
+
type: "message",
|
|
1107
|
+
messageId: stored.messageId,
|
|
1108
|
+
role: stored.role,
|
|
1109
|
+
text: stored.text,
|
|
1110
|
+
...stored.authorIdentityId ? { authorIdentityId: stored.authorIdentityId } : {},
|
|
1111
|
+
...stored.meta ? { meta: stored.meta } : {}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
];
|
|
1115
|
+
return [
|
|
1116
|
+
...messageEvent,
|
|
1117
|
+
...message.meta?.replied === true ? [
|
|
1118
|
+
{
|
|
1119
|
+
idempotencyKey: `message:${stored.messageId}:handled`,
|
|
1120
|
+
createdAtMs: handledAtMs,
|
|
1121
|
+
data: {
|
|
1122
|
+
type: "message_handled",
|
|
1123
|
+
messageId: stored.messageId
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
] : []
|
|
1127
|
+
];
|
|
1128
|
+
});
|
|
1129
|
+
await getConversationEventStore().append(args.conversationId, events);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
487
1132
|
// src/chat/state/artifacts.ts
|
|
488
1133
|
function coerceThreadArtifactsState(value) {
|
|
489
1134
|
if (!value || typeof value !== "object") {
|
|
@@ -927,6 +1572,9 @@ function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
|
927
1572
|
|
|
928
1573
|
export {
|
|
929
1574
|
buildDeterministicTurnId,
|
|
1575
|
+
buildDeterministicAssistantMessageId,
|
|
1576
|
+
ConversationTurnBoundaryError,
|
|
1577
|
+
getConversationTurnBoundaryError,
|
|
930
1578
|
CooperativeTurnYieldError,
|
|
931
1579
|
isCooperativeTurnYieldError,
|
|
932
1580
|
TurnInputCommitLostError,
|
|
@@ -938,12 +1586,14 @@ export {
|
|
|
938
1586
|
markTurnCompleted,
|
|
939
1587
|
markTurnFailed,
|
|
940
1588
|
persistWithRetry,
|
|
1589
|
+
TurnSliceLimitExceededError,
|
|
1590
|
+
buildTurnLimitResponse,
|
|
941
1591
|
loadTurnSessionRecord,
|
|
942
1592
|
persistRunningSessionRecord,
|
|
943
1593
|
persistCompletedSessionRecord,
|
|
944
1594
|
completeDeliveredTurn,
|
|
945
1595
|
persistAuthPauseSessionRecord,
|
|
946
|
-
|
|
1596
|
+
persistContinuationSessionRecord,
|
|
947
1597
|
persistYieldSessionRecord,
|
|
948
1598
|
getInterruptionMarker,
|
|
949
1599
|
canReusePendingAuthLink,
|
|
@@ -951,6 +1601,22 @@ export {
|
|
|
951
1601
|
clearPendingAuth,
|
|
952
1602
|
abandonReplacedPendingAuth,
|
|
953
1603
|
isPendingAuthLatestRequest,
|
|
1604
|
+
persistConversationMessageSummaries,
|
|
1605
|
+
estimateTextTokens,
|
|
1606
|
+
getAgentContextCompactionTriggerTokens,
|
|
1607
|
+
normalizeConversationText,
|
|
1608
|
+
updateConversationStats,
|
|
1609
|
+
upsertConversationMessage,
|
|
1610
|
+
recordDeliveredAssistantMessage,
|
|
1611
|
+
turnHasReply,
|
|
1612
|
+
markConversationMessage,
|
|
1613
|
+
buildConversationContext,
|
|
1614
|
+
getThreadTitleSourceMessage,
|
|
1615
|
+
createConversationMemoryService,
|
|
1616
|
+
isHumanConversationMessage,
|
|
1617
|
+
getConversationMessageSlackTs,
|
|
1618
|
+
hydrateConversationMessages,
|
|
1619
|
+
persistConversationMessages,
|
|
954
1620
|
coerceThreadArtifactsState,
|
|
955
1621
|
mergeArtifactsState,
|
|
956
1622
|
getPersistedSandboxState,
|