@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
package/dist/chunk-RYDF4GOX.js
DELETED
|
@@ -1,1072 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
agentStepEntrySchema,
|
|
3
|
-
contextProvenance,
|
|
4
|
-
createSqlAgentStepStore,
|
|
5
|
-
getAgentStepStore,
|
|
6
|
-
getConversationMessageStore,
|
|
7
|
-
getSqlExecutor,
|
|
8
|
-
juniorAgentSteps,
|
|
9
|
-
juniorConversationMessages,
|
|
10
|
-
juniorConversations,
|
|
11
|
-
legacyActorProvenance,
|
|
12
|
-
piMessageSchema,
|
|
13
|
-
readSessionLogEntries,
|
|
14
|
-
sanitizePostgresJson
|
|
15
|
-
} from "./chunk-BCULHGZK.js";
|
|
16
|
-
import {
|
|
17
|
-
botConfig,
|
|
18
|
-
escapeXml,
|
|
19
|
-
getStateAdapter,
|
|
20
|
-
resolveGatewayModel,
|
|
21
|
-
unescapeXml
|
|
22
|
-
} from "./chunk-M3UAVLEN.js";
|
|
23
|
-
import {
|
|
24
|
-
logWarn,
|
|
25
|
-
setSpanAttributes
|
|
26
|
-
} from "./chunk-AEWZ7F65.js";
|
|
27
|
-
import {
|
|
28
|
-
parseSlackMessageTs
|
|
29
|
-
} from "./chunk-7FBGKXPE.js";
|
|
30
|
-
|
|
31
|
-
// src/chat/conversations/legacy-import.ts
|
|
32
|
-
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
33
|
-
import { eq as eq2 } from "drizzle-orm";
|
|
34
|
-
import { z as z2 } from "zod";
|
|
35
|
-
|
|
36
|
-
// src/chat/conversations/visible-compactions.ts
|
|
37
|
-
import { isDeepStrictEqual } from "util";
|
|
38
|
-
function latestSnapshot(steps) {
|
|
39
|
-
for (let index = steps.length - 1; index >= 0; index -= 1) {
|
|
40
|
-
const entry = steps[index]?.entry;
|
|
41
|
-
if (entry?.type === "visible_context_compacted") {
|
|
42
|
-
return entry.compactions;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
async function hydrateConversationCompactions(args) {
|
|
48
|
-
const steps = await getAgentStepStore().loadHistory(args.conversationId);
|
|
49
|
-
args.conversation.compactions = latestSnapshot(steps);
|
|
50
|
-
}
|
|
51
|
-
async function persistConversationCompactions(args) {
|
|
52
|
-
const stepStore = getAgentStepStore();
|
|
53
|
-
const existing = latestSnapshot(
|
|
54
|
-
await stepStore.loadHistory(args.conversationId)
|
|
55
|
-
);
|
|
56
|
-
if (isDeepStrictEqual(existing, args.conversation.compactions)) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
await stepStore.append(args.conversationId, [
|
|
60
|
-
{
|
|
61
|
-
entry: {
|
|
62
|
-
type: "visible_context_compacted",
|
|
63
|
-
compactions: args.conversation.compactions
|
|
64
|
-
},
|
|
65
|
-
createdAtMs: args.conversation.compactions.at(-1)?.createdAtMs ?? Date.now()
|
|
66
|
-
}
|
|
67
|
-
]);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// src/chat/services/context-budget.ts
|
|
71
|
-
var COMPACTION_TRIGGER_INPUT_RATIO = 0.75;
|
|
72
|
-
var COMPACTION_OUTPUT_RESERVE_RATIO = 0.25;
|
|
73
|
-
var COMPACTION_TARGET_RATIO = 0.8;
|
|
74
|
-
var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
|
|
75
|
-
var FALLBACK_MAX_OUTPUT_TOKENS = 128e3;
|
|
76
|
-
function positiveInteger(value, fallback) {
|
|
77
|
-
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
78
|
-
}
|
|
79
|
-
function estimateTextTokens(text) {
|
|
80
|
-
return Math.ceil(text.length / 4);
|
|
81
|
-
}
|
|
82
|
-
function calculateContextCompactionTriggerTokens(model) {
|
|
83
|
-
const contextWindow = positiveInteger(
|
|
84
|
-
model.contextWindow,
|
|
85
|
-
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
86
|
-
);
|
|
87
|
-
const maxTokens = positiveInteger(
|
|
88
|
-
model.maxTokens,
|
|
89
|
-
FALLBACK_MAX_OUTPUT_TOKENS
|
|
90
|
-
);
|
|
91
|
-
const outputReserve = Math.min(
|
|
92
|
-
maxTokens,
|
|
93
|
-
Math.floor(contextWindow * COMPACTION_OUTPUT_RESERVE_RATIO)
|
|
94
|
-
);
|
|
95
|
-
const usableInputTokens = Math.max(1, contextWindow - outputReserve);
|
|
96
|
-
return Math.max(
|
|
97
|
-
1,
|
|
98
|
-
Math.floor(usableInputTokens * COMPACTION_TRIGGER_INPUT_RATIO)
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
function calculateContextCompactionTargetTokens(triggerTokens) {
|
|
102
|
-
return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
|
|
103
|
-
}
|
|
104
|
-
function getAgentContextCompactionTriggerTokens() {
|
|
105
|
-
const model = resolveGatewayModel(botConfig.modelId);
|
|
106
|
-
return calculateContextCompactionTriggerTokens({
|
|
107
|
-
contextWindow: botConfig.modelContextWindowTokens ?? model.contextWindow,
|
|
108
|
-
maxTokens: model.maxTokens
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
function getConversationContextCompactionTriggerTokens() {
|
|
112
|
-
const model = resolveGatewayModel(botConfig.fastModelId);
|
|
113
|
-
return calculateContextCompactionTriggerTokens({
|
|
114
|
-
contextWindow: model.contextWindow,
|
|
115
|
-
maxTokens: model.maxTokens
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// src/chat/services/conversation-memory.ts
|
|
120
|
-
var CONTEXT_MIN_LIVE_MESSAGES = 12;
|
|
121
|
-
var CONTEXT_COMPACTION_BATCH_SIZE = 24;
|
|
122
|
-
var CONTEXT_MAX_COMPACTIONS = 16;
|
|
123
|
-
var CONTEXT_MAX_MESSAGE_CHARS = 3200;
|
|
124
|
-
var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
125
|
-
function generateConversationId(prefix) {
|
|
126
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
127
|
-
}
|
|
128
|
-
function normalizeConversationText(text) {
|
|
129
|
-
return text.trim().replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS);
|
|
130
|
-
}
|
|
131
|
-
function buildImageContextSuffix(message, conversation) {
|
|
132
|
-
const byFileId = conversation?.vision.byFileId;
|
|
133
|
-
const imageFileIds = message.meta?.imageFileIds ?? [];
|
|
134
|
-
if (!byFileId || imageFileIds.length === 0) {
|
|
135
|
-
return "";
|
|
136
|
-
}
|
|
137
|
-
const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
|
|
138
|
-
if (summaries.length === 0) {
|
|
139
|
-
return "";
|
|
140
|
-
}
|
|
141
|
-
return ` [image context: ${summaries.join(" | ")}]`;
|
|
142
|
-
}
|
|
143
|
-
function renderConversationMessageLine(message, conversation) {
|
|
144
|
-
const displayName = conversationAuthorDisplayName(message);
|
|
145
|
-
const markers = [];
|
|
146
|
-
if (message.meta?.replied === false) {
|
|
147
|
-
markers.push(
|
|
148
|
-
`assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
if (message.meta?.explicitMention) {
|
|
152
|
-
markers.push("explicit mention");
|
|
153
|
-
}
|
|
154
|
-
const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
|
|
155
|
-
const imageContext = buildImageContextSuffix(message, conversation);
|
|
156
|
-
return `[${message.role}] ${displayName}: ${message.text}${imageContext}${markerSuffix}`;
|
|
157
|
-
}
|
|
158
|
-
function conversationAuthorDisplayName(message) {
|
|
159
|
-
const author = message.author;
|
|
160
|
-
const fullName = authorDisplayField(author?.fullName, author?.userId);
|
|
161
|
-
const userName = authorDisplayField(author?.userName, author?.userId);
|
|
162
|
-
return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
|
|
163
|
-
}
|
|
164
|
-
function authorDisplayField(value, userId) {
|
|
165
|
-
if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
|
|
166
|
-
return void 0;
|
|
167
|
-
}
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
function updateConversationStats(conversation) {
|
|
171
|
-
const contextText = buildConversationContext(conversation);
|
|
172
|
-
conversation.stats.estimatedContextTokens = estimateTextTokens(
|
|
173
|
-
contextText ?? ""
|
|
174
|
-
);
|
|
175
|
-
conversation.stats.totalMessageCount = conversation.messages.length;
|
|
176
|
-
conversation.stats.updatedAtMs = Date.now();
|
|
177
|
-
}
|
|
178
|
-
function upsertConversationMessage(conversation, message) {
|
|
179
|
-
const existingIndex = conversation.messages.findIndex(
|
|
180
|
-
(entry) => entry.id === message.id
|
|
181
|
-
);
|
|
182
|
-
if (existingIndex >= 0) {
|
|
183
|
-
conversation.messages[existingIndex] = {
|
|
184
|
-
...conversation.messages[existingIndex],
|
|
185
|
-
...message,
|
|
186
|
-
meta: {
|
|
187
|
-
...conversation.messages[existingIndex]?.meta,
|
|
188
|
-
...message.meta
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
updateConversationStats(conversation);
|
|
192
|
-
return message.id;
|
|
193
|
-
}
|
|
194
|
-
conversation.messages.push(message);
|
|
195
|
-
updateConversationStats(conversation);
|
|
196
|
-
return message.id;
|
|
197
|
-
}
|
|
198
|
-
function markConversationMessage(conversation, messageId, patch) {
|
|
199
|
-
if (!messageId) return;
|
|
200
|
-
const messageIndex = conversation.messages.findIndex(
|
|
201
|
-
(entry) => entry.id === messageId
|
|
202
|
-
);
|
|
203
|
-
if (messageIndex < 0) return;
|
|
204
|
-
const current = conversation.messages[messageIndex];
|
|
205
|
-
conversation.messages[messageIndex] = {
|
|
206
|
-
...current,
|
|
207
|
-
meta: {
|
|
208
|
-
...current.meta ?? {},
|
|
209
|
-
...patch
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
updateConversationStats(conversation);
|
|
213
|
-
}
|
|
214
|
-
function buildConversationContext(conversation, options = {}) {
|
|
215
|
-
const messages = conversation.messages.filter(
|
|
216
|
-
(entry) => entry.id !== options.excludeMessageId
|
|
217
|
-
);
|
|
218
|
-
if (messages.length === 0 && conversation.compactions.length === 0) {
|
|
219
|
-
return void 0;
|
|
220
|
-
}
|
|
221
|
-
const lines = [];
|
|
222
|
-
if (conversation.compactions.length > 0) {
|
|
223
|
-
lines.push("<thread-compactions>");
|
|
224
|
-
for (const [index, compaction] of conversation.compactions.entries()) {
|
|
225
|
-
lines.push(
|
|
226
|
-
` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageIds.length}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
|
|
227
|
-
compaction.summary,
|
|
228
|
-
" </compaction>"
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
lines.push("</thread-compactions>");
|
|
232
|
-
}
|
|
233
|
-
if (messages.length > 0) {
|
|
234
|
-
if (lines.length > 0) {
|
|
235
|
-
lines.push("");
|
|
236
|
-
}
|
|
237
|
-
lines.push("<thread-transcript>");
|
|
238
|
-
for (const [index, message] of messages.entries()) {
|
|
239
|
-
const author = escapeXml(conversationAuthorDisplayName(message));
|
|
240
|
-
const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
|
|
241
|
-
const ts = new Date(message.createdAtMs).toISOString();
|
|
242
|
-
const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
|
|
243
|
-
lines.push(
|
|
244
|
-
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
|
|
245
|
-
renderConversationMessageLine(message, conversation),
|
|
246
|
-
" </message>"
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
lines.push("</thread-transcript>");
|
|
250
|
-
}
|
|
251
|
-
return lines.join("\n");
|
|
252
|
-
}
|
|
253
|
-
function pruneCompactions(compactions) {
|
|
254
|
-
if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
|
|
255
|
-
return compactions;
|
|
256
|
-
}
|
|
257
|
-
const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
|
|
258
|
-
const merged = compactions.slice(0, overflowCount);
|
|
259
|
-
const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
|
|
260
|
-
const mergedIds = merged.flatMap((entry) => entry.coveredMessageIds).slice(0, 500);
|
|
261
|
-
const compacted = {
|
|
262
|
-
id: generateConversationId("compaction"),
|
|
263
|
-
createdAtMs: Date.now(),
|
|
264
|
-
summary: mergedSummary,
|
|
265
|
-
coveredMessageIds: mergedIds
|
|
266
|
-
};
|
|
267
|
-
return [compacted, ...compactions.slice(overflowCount)];
|
|
268
|
-
}
|
|
269
|
-
async function summarizeConversationChunk(messages, conversation, context, deps) {
|
|
270
|
-
const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
|
|
271
|
-
try {
|
|
272
|
-
const result = await deps.completeText({
|
|
273
|
-
modelId: botConfig.fastModelId,
|
|
274
|
-
temperature: 0,
|
|
275
|
-
messages: [
|
|
276
|
-
{
|
|
277
|
-
role: "user",
|
|
278
|
-
content: [
|
|
279
|
-
"Summarize the following older Slack thread transcript segment for future assistant turns.",
|
|
280
|
-
"Keep the summary factual and concise. Do not invent details.",
|
|
281
|
-
"",
|
|
282
|
-
"Output exactly three XML sections in this order:",
|
|
283
|
-
"<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>",
|
|
284
|
-
"<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>",
|
|
285
|
-
"<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>",
|
|
286
|
-
"",
|
|
287
|
-
"Do not output any text outside the three sections.",
|
|
288
|
-
"",
|
|
289
|
-
transcript
|
|
290
|
-
].join("\n"),
|
|
291
|
-
timestamp: Date.now()
|
|
292
|
-
}
|
|
293
|
-
],
|
|
294
|
-
metadata: {
|
|
295
|
-
modelId: botConfig.fastModelId,
|
|
296
|
-
threadId: context.threadId ?? "",
|
|
297
|
-
channelId: context.channelId ?? "",
|
|
298
|
-
actorId: context.actorId ?? "",
|
|
299
|
-
runId: context.runId ?? ""
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
const summary = result.text.trim();
|
|
303
|
-
if (summary.length > 0) {
|
|
304
|
-
return summary.slice(0, 3500);
|
|
305
|
-
}
|
|
306
|
-
} catch (error) {
|
|
307
|
-
logWarn(
|
|
308
|
-
"conversation_compaction_summary_failed",
|
|
309
|
-
{
|
|
310
|
-
slackThreadId: context.threadId,
|
|
311
|
-
slackUserId: context.actorId,
|
|
312
|
-
slackChannelId: context.channelId,
|
|
313
|
-
runId: context.runId,
|
|
314
|
-
assistantUserName: botConfig.userName,
|
|
315
|
-
modelId: botConfig.fastModelId
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
"exception.message": error instanceof Error ? error.message : String(error),
|
|
319
|
-
"app.compaction_messages_covered": messages.length
|
|
320
|
-
},
|
|
321
|
-
"Compaction summarization failed; using fallback summary"
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
return transcript.slice(0, 2800);
|
|
325
|
-
}
|
|
326
|
-
async function generateThreadTitleWithDeps(sourceText, deps) {
|
|
327
|
-
const result = await deps.completeText({
|
|
328
|
-
modelId: botConfig.fastModelId,
|
|
329
|
-
temperature: 0,
|
|
330
|
-
messages: [
|
|
331
|
-
{
|
|
332
|
-
role: "user",
|
|
333
|
-
content: [
|
|
334
|
-
"Generate a concise 5-8 word Slack conversation title from the first user message below.",
|
|
335
|
-
"Capture the user's main request.",
|
|
336
|
-
"Reply with ONLY the title, with no quotes or trailing punctuation.",
|
|
337
|
-
"",
|
|
338
|
-
`First user message: ${sourceText.slice(0, 500)}`
|
|
339
|
-
].join("\n"),
|
|
340
|
-
timestamp: Date.now()
|
|
341
|
-
}
|
|
342
|
-
],
|
|
343
|
-
metadata: {
|
|
344
|
-
modelId: botConfig.fastModelId
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
return result.text.trim().slice(0, 60);
|
|
348
|
-
}
|
|
349
|
-
function getThreadTitleSourceMessage(conversation) {
|
|
350
|
-
let firstMessage;
|
|
351
|
-
for (const message of conversation.messages) {
|
|
352
|
-
if (!isHumanConversationMessage(message)) {
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (!firstMessage) {
|
|
356
|
-
firstMessage = message;
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
if (message.createdAtMs < firstMessage.createdAtMs) {
|
|
360
|
-
firstMessage = message;
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
|
|
364
|
-
firstMessage = message;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return firstMessage;
|
|
368
|
-
}
|
|
369
|
-
async function compactConversationIfNeededWithDeps(conversation, context, deps) {
|
|
370
|
-
updateConversationStats(conversation);
|
|
371
|
-
let estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
372
|
-
setSpanAttributes({
|
|
373
|
-
"app.context_tokens_estimated": estimatedTokens
|
|
374
|
-
});
|
|
375
|
-
const triggerTokens = getConversationContextCompactionTriggerTokens();
|
|
376
|
-
const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
|
|
377
|
-
while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
|
|
378
|
-
const compactCount = Math.min(
|
|
379
|
-
CONTEXT_COMPACTION_BATCH_SIZE,
|
|
380
|
-
conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
|
|
381
|
-
);
|
|
382
|
-
if (compactCount <= 0) {
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
const compactedChunk = conversation.messages.slice(0, compactCount);
|
|
386
|
-
const summary = await summarizeConversationChunk(
|
|
387
|
-
compactedChunk,
|
|
388
|
-
conversation,
|
|
389
|
-
context,
|
|
390
|
-
deps
|
|
391
|
-
);
|
|
392
|
-
conversation.compactions.push({
|
|
393
|
-
id: generateConversationId("compaction"),
|
|
394
|
-
createdAtMs: Date.now(),
|
|
395
|
-
summary,
|
|
396
|
-
coveredMessageIds: compactedChunk.map((entry) => entry.id)
|
|
397
|
-
});
|
|
398
|
-
conversation.compactions = pruneCompactions(conversation.compactions);
|
|
399
|
-
conversation.messages = conversation.messages.slice(compactCount);
|
|
400
|
-
conversation.stats.compactedMessageCount += compactCount;
|
|
401
|
-
updateConversationStats(conversation);
|
|
402
|
-
estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
403
|
-
setSpanAttributes({
|
|
404
|
-
"app.compaction_messages_covered": compactCount,
|
|
405
|
-
"app.compaction.trigger_tokens": triggerTokens,
|
|
406
|
-
"app.compaction.target_tokens": targetTokens,
|
|
407
|
-
"app.context_tokens_estimated": estimatedTokens
|
|
408
|
-
});
|
|
409
|
-
if (estimatedTokens <= targetTokens) {
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
function createConversationMemoryService(deps) {
|
|
415
|
-
return {
|
|
416
|
-
compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps),
|
|
417
|
-
generateThreadTitle: async (sourceText) => await generateThreadTitleWithDeps(sourceText, deps)
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
function isHumanConversationMessage(message) {
|
|
421
|
-
return message.role === "user" && message.author?.isBot !== true;
|
|
422
|
-
}
|
|
423
|
-
function getConversationMessageSlackTs(message) {
|
|
424
|
-
return parseSlackMessageTs(message.meta?.slackTs);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// src/chat/conversations/visible-messages.ts
|
|
428
|
-
function toStoredConversationMessage(message) {
|
|
429
|
-
const meta = {};
|
|
430
|
-
if (message.author) {
|
|
431
|
-
meta.author = message.author;
|
|
432
|
-
}
|
|
433
|
-
const { replied, ...restMeta } = message.meta ?? {};
|
|
434
|
-
Object.assign(meta, restMeta);
|
|
435
|
-
if (replied === false) {
|
|
436
|
-
meta.replied = false;
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
messageId: message.id,
|
|
440
|
-
role: message.role,
|
|
441
|
-
text: message.text,
|
|
442
|
-
...Object.keys(meta).length > 0 ? { meta } : {},
|
|
443
|
-
createdAtMs: message.createdAtMs
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
function fromStoredMessage(row) {
|
|
447
|
-
const rawMeta = { ...row.meta ?? {} };
|
|
448
|
-
const author = rawMeta.author;
|
|
449
|
-
delete rawMeta.author;
|
|
450
|
-
const meta = { ...rawMeta };
|
|
451
|
-
if (row.repliedAtMs !== void 0) {
|
|
452
|
-
meta.replied = true;
|
|
453
|
-
}
|
|
454
|
-
return {
|
|
455
|
-
id: row.messageId,
|
|
456
|
-
role: row.role,
|
|
457
|
-
text: row.text,
|
|
458
|
-
createdAtMs: row.createdAtMs,
|
|
459
|
-
...author ? { author } : {},
|
|
460
|
-
...Object.keys(meta).length > 0 ? { meta } : {}
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
async function hydrateConversationMessages(args) {
|
|
464
|
-
if (!args.conversationId) {
|
|
465
|
-
args.conversation.messages = [];
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
const { ensureLegacyConversationImport: ensureLegacyConversationImport2 } = await import("./legacy-import-G333RHJZ.js");
|
|
469
|
-
await ensureLegacyConversationImport2({ conversationId: args.conversationId });
|
|
470
|
-
await hydrateConversationCompactions({
|
|
471
|
-
conversation: args.conversation,
|
|
472
|
-
conversationId: args.conversationId
|
|
473
|
-
});
|
|
474
|
-
const store = getConversationMessageStore();
|
|
475
|
-
const rows = await store.list(args.conversationId);
|
|
476
|
-
const coveredIds = new Set(
|
|
477
|
-
args.conversation.compactions.flatMap(
|
|
478
|
-
(compaction) => compaction.coveredMessageIds
|
|
479
|
-
)
|
|
480
|
-
);
|
|
481
|
-
args.conversation.messages = rows.filter((row) => !coveredIds.has(row.messageId)).map(fromStoredMessage);
|
|
482
|
-
updateConversationStats(args.conversation);
|
|
483
|
-
}
|
|
484
|
-
async function persistConversationMessages(args) {
|
|
485
|
-
if (!args.conversationId || args.conversation.messages.length === 0) {
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
const store = getConversationMessageStore();
|
|
489
|
-
await store.record(
|
|
490
|
-
args.conversationId,
|
|
491
|
-
args.conversation.messages.map(toStoredConversationMessage)
|
|
492
|
-
);
|
|
493
|
-
const repliedAtMs = args.repliedAtMs ?? Date.now();
|
|
494
|
-
for (const message of args.conversation.messages) {
|
|
495
|
-
if (message.meta?.replied === true) {
|
|
496
|
-
await store.markReplied(args.conversationId, message.id, repliedAtMs);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// src/chat/conversations/legacy-advisor-session.ts
|
|
502
|
-
import { z } from "zod";
|
|
503
|
-
var legacyAdvisorMessagesSchema = z.array(piMessageSchema);
|
|
504
|
-
function key(conversationId) {
|
|
505
|
-
return `junior:${conversationId}:advisor_session`;
|
|
506
|
-
}
|
|
507
|
-
function createLegacyAdvisorSessionReader() {
|
|
508
|
-
return {
|
|
509
|
-
load: async (conversationId) => {
|
|
510
|
-
const stateAdapter = getStateAdapter();
|
|
511
|
-
await stateAdapter.connect();
|
|
512
|
-
return legacyAdvisorMessagesSchema.parse(
|
|
513
|
-
structuredClone(
|
|
514
|
-
await stateAdapter.get(key(conversationId)) ?? []
|
|
515
|
-
)
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// src/chat/conversations/sql/legacy-history-import.ts
|
|
522
|
-
import { eq, sql } from "drizzle-orm";
|
|
523
|
-
var INITIAL_SESSION_ID = "session_0";
|
|
524
|
-
var ADVISOR_TASK_OPEN = "<advisor-task>\n";
|
|
525
|
-
var ADVISOR_TASK_CLOSE = "\n</advisor-task>";
|
|
526
|
-
var ADVISOR_CONTEXT_OPEN = "<executor-context>\n";
|
|
527
|
-
var ADVISOR_CONTEXT_CLOSE = "\n</executor-context>";
|
|
528
|
-
function importedAdvisorChildConversationId(parentConversationId) {
|
|
529
|
-
return `advisor:${parentConversationId}`;
|
|
530
|
-
}
|
|
531
|
-
function epochFromSessionId(sessionId) {
|
|
532
|
-
const match = /^session_(\d+)$/.exec(sessionId);
|
|
533
|
-
return match ? Number(match[1]) : 0;
|
|
534
|
-
}
|
|
535
|
-
function messageTimestampMs(message) {
|
|
536
|
-
const timestamp = message.timestamp;
|
|
537
|
-
return typeof timestamp === "number" ? timestamp : void 0;
|
|
538
|
-
}
|
|
539
|
-
function readAdvisorRequest(text) {
|
|
540
|
-
if (!text.startsWith(ADVISOR_TASK_OPEN) || !text.endsWith(ADVISOR_CONTEXT_CLOSE)) {
|
|
541
|
-
return void 0;
|
|
542
|
-
}
|
|
543
|
-
const taskEnd = text.indexOf(ADVISOR_TASK_CLOSE, ADVISOR_TASK_OPEN.length);
|
|
544
|
-
if (taskEnd < 0) {
|
|
545
|
-
return void 0;
|
|
546
|
-
}
|
|
547
|
-
const contextStart = taskEnd + ADVISOR_TASK_CLOSE.length + 2;
|
|
548
|
-
if (!text.startsWith(ADVISOR_CONTEXT_OPEN, contextStart)) {
|
|
549
|
-
return void 0;
|
|
550
|
-
}
|
|
551
|
-
const task = text.slice(ADVISOR_TASK_OPEN.length, taskEnd);
|
|
552
|
-
const context = text.slice(
|
|
553
|
-
contextStart + ADVISOR_CONTEXT_OPEN.length,
|
|
554
|
-
-ADVISOR_CONTEXT_CLOSE.length
|
|
555
|
-
);
|
|
556
|
-
return `${unescapeXml(task)}
|
|
557
|
-
|
|
558
|
-
Executor context:
|
|
559
|
-
${unescapeXml(context)}`;
|
|
560
|
-
}
|
|
561
|
-
function normalizeAdvisorMessage(message) {
|
|
562
|
-
const record = message;
|
|
563
|
-
if (record.role !== "user" || !Array.isArray(record.content)) {
|
|
564
|
-
return message;
|
|
565
|
-
}
|
|
566
|
-
let changed = false;
|
|
567
|
-
const content = record.content.map((part) => {
|
|
568
|
-
if (!part || typeof part !== "object" || part.type !== "text" || typeof part.text !== "string") {
|
|
569
|
-
return part;
|
|
570
|
-
}
|
|
571
|
-
const text = readAdvisorRequest(part.text);
|
|
572
|
-
if (text === void 0) {
|
|
573
|
-
return part;
|
|
574
|
-
}
|
|
575
|
-
changed = true;
|
|
576
|
-
return { ...part, text };
|
|
577
|
-
});
|
|
578
|
-
return changed ? { ...record, content } : message;
|
|
579
|
-
}
|
|
580
|
-
function piEntryProvenance(entry) {
|
|
581
|
-
if (entry.provenance) {
|
|
582
|
-
return entry.provenance;
|
|
583
|
-
}
|
|
584
|
-
if (entry.actor) {
|
|
585
|
-
return legacyActorProvenance(entry.actor);
|
|
586
|
-
}
|
|
587
|
-
return contextProvenance;
|
|
588
|
-
}
|
|
589
|
-
function convertLegacySessionLog(args) {
|
|
590
|
-
const steps = [];
|
|
591
|
-
const fallback = args.fallbackCreatedAtMs;
|
|
592
|
-
let advisorChildConversationId;
|
|
593
|
-
let seq = 0;
|
|
594
|
-
const push = (contextEpoch, entry, createdAtMs) => {
|
|
595
|
-
steps.push({ seq, contextEpoch, entry, createdAtMs });
|
|
596
|
-
seq += 1;
|
|
597
|
-
};
|
|
598
|
-
for (const entry of args.entries) {
|
|
599
|
-
const epoch = epochFromSessionId(entry.sessionId ?? INITIAL_SESSION_ID);
|
|
600
|
-
switch (entry.type) {
|
|
601
|
-
case "pi_message": {
|
|
602
|
-
push(
|
|
603
|
-
epoch,
|
|
604
|
-
{
|
|
605
|
-
type: "pi_message",
|
|
606
|
-
message: entry.message,
|
|
607
|
-
provenance: piEntryProvenance(entry)
|
|
608
|
-
},
|
|
609
|
-
messageTimestampMs(entry.message) ?? fallback
|
|
610
|
-
);
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
case "projection_reset": {
|
|
614
|
-
const provenance = entry.provenance ?? entry.messages.map(() => contextProvenance);
|
|
615
|
-
if (provenance.length !== entry.messages.length) {
|
|
616
|
-
throw new Error(
|
|
617
|
-
"projection_reset provenance must align one-to-one with messages"
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
push(
|
|
621
|
-
epoch,
|
|
622
|
-
{ type: "context_epoch_started", reason: "compaction" },
|
|
623
|
-
fallback
|
|
624
|
-
);
|
|
625
|
-
entry.messages.forEach((message, index) => {
|
|
626
|
-
push(
|
|
627
|
-
epoch,
|
|
628
|
-
{
|
|
629
|
-
type: "pi_message",
|
|
630
|
-
message,
|
|
631
|
-
provenance: provenance[index]
|
|
632
|
-
},
|
|
633
|
-
messageTimestampMs(message) ?? fallback
|
|
634
|
-
);
|
|
635
|
-
});
|
|
636
|
-
break;
|
|
637
|
-
}
|
|
638
|
-
case "mcp_provider_connected": {
|
|
639
|
-
push(
|
|
640
|
-
epoch,
|
|
641
|
-
{ type: "mcp_provider_connected", provider: entry.provider },
|
|
642
|
-
fallback
|
|
643
|
-
);
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
case "authorization_requested": {
|
|
647
|
-
push(
|
|
648
|
-
epoch,
|
|
649
|
-
{
|
|
650
|
-
type: "authorization_requested",
|
|
651
|
-
kind: entry.kind,
|
|
652
|
-
provider: entry.provider,
|
|
653
|
-
actorId: entry.actorId,
|
|
654
|
-
authorizationId: entry.authorizationId,
|
|
655
|
-
delivery: entry.delivery
|
|
656
|
-
},
|
|
657
|
-
entry.createdAtMs
|
|
658
|
-
);
|
|
659
|
-
break;
|
|
660
|
-
}
|
|
661
|
-
case "authorization_completed": {
|
|
662
|
-
push(
|
|
663
|
-
epoch,
|
|
664
|
-
{
|
|
665
|
-
type: "authorization_completed",
|
|
666
|
-
kind: entry.kind,
|
|
667
|
-
provider: entry.provider,
|
|
668
|
-
actorId: entry.actorId,
|
|
669
|
-
authorizationId: entry.authorizationId
|
|
670
|
-
},
|
|
671
|
-
entry.createdAtMs
|
|
672
|
-
);
|
|
673
|
-
break;
|
|
674
|
-
}
|
|
675
|
-
case "tool_execution_started": {
|
|
676
|
-
push(
|
|
677
|
-
epoch,
|
|
678
|
-
{
|
|
679
|
-
type: "tool_execution_started",
|
|
680
|
-
toolCallId: entry.toolCallId,
|
|
681
|
-
toolName: entry.toolName,
|
|
682
|
-
...entry.args !== void 0 ? { args: entry.args } : {}
|
|
683
|
-
},
|
|
684
|
-
entry.createdAtMs
|
|
685
|
-
);
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
case "subagent_started": {
|
|
689
|
-
const childConversationId = importedAdvisorChildConversationId(
|
|
690
|
-
args.conversationId
|
|
691
|
-
);
|
|
692
|
-
advisorChildConversationId = childConversationId;
|
|
693
|
-
push(
|
|
694
|
-
epoch,
|
|
695
|
-
{
|
|
696
|
-
type: "subagent_started",
|
|
697
|
-
subagentInvocationId: entry.subagentInvocationId,
|
|
698
|
-
subagentKind: entry.subagentKind,
|
|
699
|
-
...entry.parentToolCallId ? { parentToolCallId: entry.parentToolCallId } : {},
|
|
700
|
-
childConversationId,
|
|
701
|
-
historyMode: "shared"
|
|
702
|
-
},
|
|
703
|
-
entry.createdAtMs
|
|
704
|
-
);
|
|
705
|
-
break;
|
|
706
|
-
}
|
|
707
|
-
case "subagent_ended": {
|
|
708
|
-
push(
|
|
709
|
-
epoch,
|
|
710
|
-
{
|
|
711
|
-
type: "subagent_ended",
|
|
712
|
-
subagentInvocationId: entry.subagentInvocationId,
|
|
713
|
-
outcome: entry.outcome,
|
|
714
|
-
...entry.errorCode ? { errorCode: entry.errorCode } : {}
|
|
715
|
-
},
|
|
716
|
-
entry.createdAtMs
|
|
717
|
-
);
|
|
718
|
-
break;
|
|
719
|
-
}
|
|
720
|
-
case "actor_recorded": {
|
|
721
|
-
break;
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
return {
|
|
726
|
-
steps,
|
|
727
|
-
...advisorChildConversationId ? { advisorChildConversationId } : {}
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
function convertAdvisorMessages(messages, fallbackCreatedAtMs) {
|
|
731
|
-
return messages.map((sourceMessage, seq) => {
|
|
732
|
-
const message = normalizeAdvisorMessage(sourceMessage);
|
|
733
|
-
return {
|
|
734
|
-
seq,
|
|
735
|
-
contextEpoch: 0,
|
|
736
|
-
entry: { type: "pi_message", message, provenance: contextProvenance },
|
|
737
|
-
createdAtMs: messageTimestampMs(message) ?? fallbackCreatedAtMs
|
|
738
|
-
};
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
function messageRole(entry) {
|
|
742
|
-
if (entry.type !== "pi_message") {
|
|
743
|
-
return null;
|
|
744
|
-
}
|
|
745
|
-
const role = entry.message.role;
|
|
746
|
-
return typeof role === "string" ? role : null;
|
|
747
|
-
}
|
|
748
|
-
function insertRow(conversationId, step) {
|
|
749
|
-
const { type, ...payload } = agentStepEntrySchema.parse(step.entry);
|
|
750
|
-
return {
|
|
751
|
-
conversationId,
|
|
752
|
-
seq: step.seq,
|
|
753
|
-
contextEpoch: step.contextEpoch,
|
|
754
|
-
type,
|
|
755
|
-
role: messageRole(step.entry),
|
|
756
|
-
payload: sanitizePostgresJson(payload),
|
|
757
|
-
createdAt: new Date(step.createdAtMs)
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
async function writeLegacyImport(executor, args) {
|
|
761
|
-
return executor.withLock(
|
|
762
|
-
`junior_conversation:legacy-import:${args.conversationId}`,
|
|
763
|
-
() => executor.transaction(async () => {
|
|
764
|
-
const db = executor.db();
|
|
765
|
-
const conversations = await db.select({
|
|
766
|
-
transcriptPurgedAt: juniorConversations.transcriptPurgedAt
|
|
767
|
-
}).from(juniorConversations).where(eq(juniorConversations.conversationId, args.conversationId)).for("update");
|
|
768
|
-
if (conversations[0]?.transcriptPurgedAt) {
|
|
769
|
-
return false;
|
|
770
|
-
}
|
|
771
|
-
const existing = await db.select({ seq: juniorAgentSteps.seq }).from(juniorAgentSteps).where(eq(juniorAgentSteps.conversationId, args.conversationId)).limit(1);
|
|
772
|
-
if (existing.length > 0) {
|
|
773
|
-
return false;
|
|
774
|
-
}
|
|
775
|
-
const createdAt = new Date(args.fallbackCreatedAtMs);
|
|
776
|
-
await ensureConversationRow(
|
|
777
|
-
executor,
|
|
778
|
-
args.conversationId,
|
|
779
|
-
createdAt,
|
|
780
|
-
new Date(args.lastActivityAtMs)
|
|
781
|
-
);
|
|
782
|
-
if (args.messages && args.messages.length > 0) {
|
|
783
|
-
await db.insert(juniorConversationMessages).values(
|
|
784
|
-
args.messages.map((message) => ({
|
|
785
|
-
conversationId: args.conversationId,
|
|
786
|
-
messageId: message.messageId,
|
|
787
|
-
role: message.role,
|
|
788
|
-
authorIdentityId: message.authorIdentityId ?? null,
|
|
789
|
-
text: message.text,
|
|
790
|
-
meta: message.meta ?? null,
|
|
791
|
-
repliedAt: message.repliedAtMs === void 0 ? null : new Date(message.repliedAtMs),
|
|
792
|
-
createdAt: new Date(message.createdAtMs)
|
|
793
|
-
}))
|
|
794
|
-
).onConflictDoUpdate({
|
|
795
|
-
target: [
|
|
796
|
-
juniorConversationMessages.conversationId,
|
|
797
|
-
juniorConversationMessages.messageId
|
|
798
|
-
],
|
|
799
|
-
set: {
|
|
800
|
-
meta: sql`nullif(coalesce(${juniorConversationMessages.meta}, '{}'::jsonb) || coalesce(excluded.meta, '{}'::jsonb), '{}'::jsonb)`,
|
|
801
|
-
repliedAt: sql`coalesce(${juniorConversationMessages.repliedAt}, excluded.replied_at)`
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
if (args.steps.length > 0) {
|
|
806
|
-
await db.insert(juniorAgentSteps).values(
|
|
807
|
-
args.steps.map((step) => insertRow(args.conversationId, step))
|
|
808
|
-
);
|
|
809
|
-
}
|
|
810
|
-
if (args.child) {
|
|
811
|
-
const childCreatedAtMs = args.child.steps.length > 0 ? Math.min(...args.child.steps.map((step) => step.createdAtMs)) : args.fallbackCreatedAtMs;
|
|
812
|
-
const childLastActivityAtMs = args.child.steps.length > 0 ? Math.max(...args.child.steps.map((step) => step.createdAtMs)) : childCreatedAtMs;
|
|
813
|
-
await ensureChildConversationRow(
|
|
814
|
-
executor,
|
|
815
|
-
args.child.conversationId,
|
|
816
|
-
args.conversationId,
|
|
817
|
-
new Date(childCreatedAtMs),
|
|
818
|
-
new Date(childLastActivityAtMs)
|
|
819
|
-
);
|
|
820
|
-
if (args.child.steps.length > 0) {
|
|
821
|
-
await db.insert(juniorAgentSteps).values(
|
|
822
|
-
args.child.steps.map(
|
|
823
|
-
(step) => insertRow(args.child.conversationId, step)
|
|
824
|
-
)
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
return true;
|
|
829
|
-
})
|
|
830
|
-
);
|
|
831
|
-
}
|
|
832
|
-
async function ensureConversationRow(executor, conversationId, createdAt, lastActivityAt) {
|
|
833
|
-
await executor.db().insert(juniorConversations).values({
|
|
834
|
-
conversationId,
|
|
835
|
-
schemaVersion: 1,
|
|
836
|
-
createdAt,
|
|
837
|
-
lastActivityAt,
|
|
838
|
-
updatedAt: lastActivityAt,
|
|
839
|
-
executionStatus: "idle"
|
|
840
|
-
}).onConflictDoUpdate({
|
|
841
|
-
target: juniorConversations.conversationId,
|
|
842
|
-
set: {
|
|
843
|
-
createdAt: sql`least(${juniorConversations.createdAt}, excluded.created_at)`,
|
|
844
|
-
lastActivityAt: sql`greatest(${juniorConversations.lastActivityAt}, excluded.last_activity_at)`,
|
|
845
|
-
updatedAt: sql`greatest(${juniorConversations.updatedAt}, excluded.updated_at)`
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
async function ensureChildConversationRow(executor, childConversationId, parentConversationId, createdAt, lastActivityAt) {
|
|
850
|
-
await ensureConversationRow(
|
|
851
|
-
executor,
|
|
852
|
-
parentConversationId,
|
|
853
|
-
createdAt,
|
|
854
|
-
lastActivityAt
|
|
855
|
-
);
|
|
856
|
-
await executor.db().insert(juniorConversations).values({
|
|
857
|
-
conversationId: childConversationId,
|
|
858
|
-
schemaVersion: 1,
|
|
859
|
-
parentConversationId,
|
|
860
|
-
createdAt,
|
|
861
|
-
lastActivityAt,
|
|
862
|
-
updatedAt: lastActivityAt,
|
|
863
|
-
executionStatus: "idle"
|
|
864
|
-
}).onConflictDoUpdate({
|
|
865
|
-
target: juniorConversations.conversationId,
|
|
866
|
-
set: {
|
|
867
|
-
parentConversationId: sql`coalesce(${juniorConversations.parentConversationId}, excluded.parent_conversation_id)`,
|
|
868
|
-
createdAt: sql`least(${juniorConversations.createdAt}, excluded.created_at)`,
|
|
869
|
-
lastActivityAt: sql`greatest(${juniorConversations.lastActivityAt}, excluded.last_activity_at)`,
|
|
870
|
-
updatedAt: sql`greatest(${juniorConversations.updatedAt}, excluded.updated_at)`
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// src/chat/conversations/legacy-import.ts
|
|
876
|
-
var legacyVisibleMessageSchema = z2.object({
|
|
877
|
-
id: z2.string(),
|
|
878
|
-
role: z2.enum(["user", "assistant", "system"]),
|
|
879
|
-
text: z2.string(),
|
|
880
|
-
createdAtMs: z2.number().finite(),
|
|
881
|
-
author: z2.object({}).passthrough().optional(),
|
|
882
|
-
meta: z2.object({}).passthrough().optional()
|
|
883
|
-
});
|
|
884
|
-
var legacyCompactionSchema = z2.object({
|
|
885
|
-
coveredMessageIds: z2.array(z2.string()),
|
|
886
|
-
createdAtMs: z2.number().finite(),
|
|
887
|
-
id: z2.string(),
|
|
888
|
-
summary: z2.string()
|
|
889
|
-
});
|
|
890
|
-
var legacyThreadStateSnapshotSchema = z2.object({
|
|
891
|
-
conversation: z2.object({
|
|
892
|
-
compactions: z2.array(legacyCompactionSchema).optional(),
|
|
893
|
-
messages: z2.array(legacyVisibleMessageSchema).optional(),
|
|
894
|
-
stats: z2.object({ updatedAtMs: z2.number().finite().optional() }).passthrough().optional()
|
|
895
|
-
}).passthrough().optional()
|
|
896
|
-
});
|
|
897
|
-
async function loadThreadStateSnapshot(conversationId) {
|
|
898
|
-
const stateAdapter = getStateAdapter();
|
|
899
|
-
await stateAdapter.connect();
|
|
900
|
-
const raw = await stateAdapter.get(`thread-state:${conversationId}`);
|
|
901
|
-
if (!raw) {
|
|
902
|
-
return { compactions: [], messages: [] };
|
|
903
|
-
}
|
|
904
|
-
const conversation = legacyThreadStateSnapshotSchema.parse(raw).conversation;
|
|
905
|
-
return {
|
|
906
|
-
compactions: conversation?.compactions ?? [],
|
|
907
|
-
messages: conversation?.messages ?? [],
|
|
908
|
-
...conversation?.stats?.updatedAtMs !== void 0 ? { lastActivityAtMs: conversation.stats.updatedAtMs } : {}
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
function intrinsicTimestamps(entries, visible, compactions) {
|
|
912
|
-
const candidates = [];
|
|
913
|
-
const pushMessageTs = (message) => {
|
|
914
|
-
const timestamp = message.timestamp;
|
|
915
|
-
if (typeof timestamp === "number") {
|
|
916
|
-
candidates.push(timestamp);
|
|
917
|
-
}
|
|
918
|
-
};
|
|
919
|
-
for (const entry of entries) {
|
|
920
|
-
if (entry.type === "pi_message") {
|
|
921
|
-
pushMessageTs(entry.message);
|
|
922
|
-
} else if (entry.type === "projection_reset") {
|
|
923
|
-
entry.messages.forEach(pushMessageTs);
|
|
924
|
-
} else if ("createdAtMs" in entry) {
|
|
925
|
-
candidates.push(entry.createdAtMs);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
for (const message of visible) {
|
|
929
|
-
candidates.push(message.createdAtMs);
|
|
930
|
-
}
|
|
931
|
-
for (const compaction of compactions) {
|
|
932
|
-
candidates.push(compaction.createdAtMs);
|
|
933
|
-
}
|
|
934
|
-
return candidates;
|
|
935
|
-
}
|
|
936
|
-
async function importConversationFromLegacy(conversationId, deps) {
|
|
937
|
-
const stepStore = createSqlAgentStepStore(deps.executor);
|
|
938
|
-
const existing = await stepStore.loadCurrentEpoch(conversationId);
|
|
939
|
-
if (existing.length > 0) {
|
|
940
|
-
return { imported: false };
|
|
941
|
-
}
|
|
942
|
-
const entries = deps.sessionLogStore ? await deps.sessionLogStore.read({ conversationId }) : await readSessionLogEntries({ conversationId });
|
|
943
|
-
const snapshot = deps.loadVisibleMessages ? {
|
|
944
|
-
compactions: deps.legacyCompactions ?? [],
|
|
945
|
-
messages: await deps.loadVisibleMessages(conversationId)
|
|
946
|
-
} : await loadThreadStateSnapshot(conversationId);
|
|
947
|
-
const { compactions, messages: visible } = snapshot;
|
|
948
|
-
if (entries.length === 0 && visible.length === 0 && compactions.length === 0) {
|
|
949
|
-
return { imported: false };
|
|
950
|
-
}
|
|
951
|
-
const hasAdvisor = entries.some((entry) => entry.type === "subagent_started");
|
|
952
|
-
const advisorMessages = hasAdvisor ? await (deps.advisorSessionStore ?? createLegacyAdvisorSessionReader()).load(conversationId) : [];
|
|
953
|
-
const intrinsic = intrinsicTimestamps(entries, visible, compactions);
|
|
954
|
-
for (const message of advisorMessages) {
|
|
955
|
-
const timestamp = message.timestamp;
|
|
956
|
-
if (typeof timestamp === "number") {
|
|
957
|
-
intrinsic.push(timestamp);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
const fallbackCreatedAtMs = deps.conversationRecord?.createdAtMs ?? (intrinsic.length > 0 ? Math.min(...intrinsic) : void 0) ?? 0;
|
|
961
|
-
const lastActivityAtMs = Math.max(
|
|
962
|
-
fallbackCreatedAtMs,
|
|
963
|
-
deps.conversationRecord?.lastActivityAtMs ?? 0,
|
|
964
|
-
deps.legacyLastActivityAtMs ?? 0,
|
|
965
|
-
intrinsic.length > 0 ? Math.max(...intrinsic) : 0
|
|
966
|
-
);
|
|
967
|
-
const converted = convertLegacySessionLog({
|
|
968
|
-
conversationId,
|
|
969
|
-
entries,
|
|
970
|
-
fallbackCreatedAtMs
|
|
971
|
-
});
|
|
972
|
-
if (compactions.length > 0) {
|
|
973
|
-
converted.steps.push({
|
|
974
|
-
seq: converted.steps.length,
|
|
975
|
-
contextEpoch: converted.steps.at(-1)?.contextEpoch ?? 0,
|
|
976
|
-
entry: { type: "visible_context_compacted", compactions },
|
|
977
|
-
createdAtMs: compactions.at(-1)?.createdAtMs ?? fallbackCreatedAtMs
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
if (converted.steps.length === 0 && visible.length > 0) {
|
|
981
|
-
const existingMessages = new Map(
|
|
982
|
-
(await deps.messageStore.list(conversationId)).map((message) => [
|
|
983
|
-
message.messageId,
|
|
984
|
-
message
|
|
985
|
-
])
|
|
986
|
-
);
|
|
987
|
-
const fullyImported = visible.every((message) => {
|
|
988
|
-
const existingMessage = existingMessages.get(message.id);
|
|
989
|
-
const projected = toStoredConversationMessage(message);
|
|
990
|
-
return existingMessage !== void 0 && Object.entries(projected.meta ?? {}).every(
|
|
991
|
-
([key2, value]) => isDeepStrictEqual2(existingMessage.meta?.[key2], value)
|
|
992
|
-
) && (message.meta?.replied !== true || existingMessage.repliedAtMs !== void 0);
|
|
993
|
-
});
|
|
994
|
-
if (fullyImported) {
|
|
995
|
-
await writeLegacyImport(deps.executor, {
|
|
996
|
-
conversationId,
|
|
997
|
-
fallbackCreatedAtMs,
|
|
998
|
-
lastActivityAtMs,
|
|
999
|
-
steps: []
|
|
1000
|
-
});
|
|
1001
|
-
return { imported: false };
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
let child;
|
|
1005
|
-
if (converted.advisorChildConversationId) {
|
|
1006
|
-
child = {
|
|
1007
|
-
conversationId: converted.advisorChildConversationId,
|
|
1008
|
-
steps: convertAdvisorMessages(advisorMessages, fallbackCreatedAtMs)
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
const messages = visible.map((message) => ({
|
|
1012
|
-
...toStoredConversationMessage(message),
|
|
1013
|
-
...message.meta?.replied ? { repliedAtMs: message.createdAtMs } : {}
|
|
1014
|
-
}));
|
|
1015
|
-
const imported = await writeLegacyImport(deps.executor, {
|
|
1016
|
-
conversationId,
|
|
1017
|
-
fallbackCreatedAtMs,
|
|
1018
|
-
lastActivityAtMs,
|
|
1019
|
-
...messages.length > 0 ? { messages } : {},
|
|
1020
|
-
steps: converted.steps,
|
|
1021
|
-
...child ? { child } : {}
|
|
1022
|
-
});
|
|
1023
|
-
return { imported };
|
|
1024
|
-
}
|
|
1025
|
-
async function ensureLegacyConversationImport(args) {
|
|
1026
|
-
const stepStore = getAgentStepStore();
|
|
1027
|
-
if ((await stepStore.loadCurrentEpoch(args.conversationId)).length > 0) {
|
|
1028
|
-
return;
|
|
1029
|
-
}
|
|
1030
|
-
const entries = await readSessionLogEntries({
|
|
1031
|
-
conversationId: args.conversationId
|
|
1032
|
-
});
|
|
1033
|
-
const snapshot = await loadThreadStateSnapshot(args.conversationId);
|
|
1034
|
-
const visible = snapshot.messages;
|
|
1035
|
-
if (entries.length === 0 && visible.length === 0 && snapshot.compactions.length === 0) {
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
|
-
const executor = getSqlExecutor();
|
|
1039
|
-
const purged = await executor.db().select({ transcriptPurgedAt: juniorConversations.transcriptPurgedAt }).from(juniorConversations).where(eq2(juniorConversations.conversationId, args.conversationId));
|
|
1040
|
-
if (purged[0]?.transcriptPurgedAt) {
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
await importConversationFromLegacy(args.conversationId, {
|
|
1044
|
-
executor,
|
|
1045
|
-
messageStore: getConversationMessageStore(),
|
|
1046
|
-
sessionLogStore: { read: async () => entries, append: async () => {
|
|
1047
|
-
} },
|
|
1048
|
-
loadVisibleMessages: async () => visible,
|
|
1049
|
-
legacyCompactions: snapshot.compactions,
|
|
1050
|
-
...snapshot.lastActivityAtMs === void 0 ? {} : { legacyLastActivityAtMs: snapshot.lastActivityAtMs }
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
export {
|
|
1055
|
-
persistConversationCompactions,
|
|
1056
|
-
estimateTextTokens,
|
|
1057
|
-
getAgentContextCompactionTriggerTokens,
|
|
1058
|
-
generateConversationId,
|
|
1059
|
-
normalizeConversationText,
|
|
1060
|
-
updateConversationStats,
|
|
1061
|
-
upsertConversationMessage,
|
|
1062
|
-
markConversationMessage,
|
|
1063
|
-
buildConversationContext,
|
|
1064
|
-
getThreadTitleSourceMessage,
|
|
1065
|
-
createConversationMemoryService,
|
|
1066
|
-
isHumanConversationMessage,
|
|
1067
|
-
getConversationMessageSlackTs,
|
|
1068
|
-
hydrateConversationMessages,
|
|
1069
|
-
persistConversationMessages,
|
|
1070
|
-
importConversationFromLegacy,
|
|
1071
|
-
ensureLegacyConversationImport
|
|
1072
|
-
};
|