@sentry/junior 0.56.0 → 0.58.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/app.js +4239 -5724
- package/dist/chat/agent-dispatch/context.d.ts +1 -0
- package/dist/chat/agent-dispatch/types.d.ts +0 -1
- package/dist/chat/conversation-privacy.d.ts +23 -0
- package/dist/chat/logging.d.ts +2 -0
- package/dist/chat/mcp/tool-manager.d.ts +18 -5
- package/dist/chat/mcp/tool-name.d.ts +2 -0
- package/dist/chat/pi/client.d.ts +2 -0
- package/dist/chat/pi/derived-state.d.ts +5 -0
- package/dist/chat/pi/traced-stream.d.ts +5 -1
- package/dist/chat/plugins/state.d.ts +4 -1
- package/dist/chat/prompt.d.ts +3 -9
- package/dist/chat/respond-helpers.d.ts +5 -3
- package/dist/chat/respond.d.ts +1 -0
- package/dist/chat/runtime/conversation-message.d.ts +10 -0
- package/dist/chat/runtime/processing-reaction.d.ts +2 -4
- package/dist/chat/runtime/reply-executor.d.ts +13 -16
- package/dist/chat/runtime/slack-runtime.d.ts +19 -32
- package/dist/chat/runtime/thread-state.d.ts +1 -1
- package/dist/chat/runtime/turn-input.d.ts +29 -0
- package/dist/chat/runtime/turn-preparation.d.ts +4 -24
- package/dist/chat/runtime/turn.d.ts +2 -3
- package/dist/chat/sentry-links.d.ts +4 -0
- package/dist/chat/services/context-compaction.d.ts +3 -4
- package/dist/chat/services/pending-auth.d.ts +1 -1
- package/dist/chat/services/subscribed-reply-policy.d.ts +2 -13
- package/dist/chat/services/timeout-resume.d.ts +1 -2
- package/dist/chat/services/turn-session-record.d.ts +82 -0
- package/dist/chat/slack/assistant-thread/title.d.ts +4 -1
- package/dist/chat/state/artifacts.d.ts +1 -0
- package/dist/chat/state/conversation.d.ts +0 -1
- package/dist/chat/state/session-log.d.ts +117 -0
- package/dist/chat/state/ttl.d.ts +2 -0
- package/dist/chat/state/turn-session.d.ts +89 -0
- package/dist/chat/tools/advisor/tool.d.ts +2 -0
- package/dist/chat/tools/agent-tools.d.ts +2 -1
- package/dist/chat/tools/skill/call-mcp-tool.d.ts +7 -3
- package/dist/chat/tools/skill/search-mcp-tools.d.ts +15 -3
- package/dist/chat/tools/types.d.ts +0 -1
- package/dist/{chunk-AA5TIFN5.js → chunk-FKEKRBUB.js} +267 -735
- package/dist/{chunk-TTUY467K.js → chunk-H652GMDH.js} +30 -14
- package/dist/chunk-I4FDGMFI.js +950 -0
- package/dist/{chunk-D3G3YOU4.js → chunk-ITOW4DED.js} +1 -1
- package/dist/chunk-QDGD5WVN.js +708 -0
- package/dist/cli/check.js +2 -2
- package/dist/cli/init.js +0 -1
- package/dist/cli/snapshot-warmup.js +5 -3
- package/dist/instrumentation.js +3 -0
- package/dist/reporting.d.ts +113 -0
- package/dist/reporting.js +390 -0
- package/package.json +26 -11
- package/dist/chat/scheduler/cadence.d.ts +0 -24
- package/dist/chat/scheduler/plugin.d.ts +0 -2
- package/dist/chat/scheduler/prompt.d.ts +0 -7
- package/dist/chat/scheduler/store.d.ts +0 -49
- package/dist/chat/scheduler/types.d.ts +0 -86
- package/dist/chat/services/turn-checkpoint.d.ts +0 -74
- package/dist/chat/state/pi-session-message-store.d.ts +0 -15
- package/dist/chat/state/turn-session-store.d.ts +0 -49
- package/dist/chat/tools/slack/schedule-tools.d.ts +0 -29
- package/dist/handlers/diagnostics-dashboard.d.ts +0 -2
- package/dist/handlers/diagnostics.d.ts +0 -2
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getChatConfig,
|
|
3
|
+
getConnectedStateContext,
|
|
4
|
+
getStateAdapter
|
|
5
|
+
} from "./chunk-FKEKRBUB.js";
|
|
6
|
+
import {
|
|
7
|
+
isRecord
|
|
8
|
+
} from "./chunk-H652GMDH.js";
|
|
9
|
+
import {
|
|
10
|
+
sentry_exports
|
|
11
|
+
} from "./chunk-Z3YD6NHK.js";
|
|
12
|
+
|
|
13
|
+
// src/handlers/health.ts
|
|
14
|
+
function GET() {
|
|
15
|
+
return Response.json({
|
|
16
|
+
status: "ok",
|
|
17
|
+
service: "junior",
|
|
18
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/chat/state/turn-session.ts
|
|
23
|
+
import { THREAD_STATE_TTL_MS } from "chat";
|
|
24
|
+
|
|
25
|
+
// src/chat/state/session-log.ts
|
|
26
|
+
import { isDeepStrictEqual } from "util";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
var AGENT_SESSION_LOG_PREFIX = "junior:agent-session-log";
|
|
29
|
+
var AGENT_SESSION_LOG_SCHEMA_VERSION = 1;
|
|
30
|
+
var INITIAL_SESSION_ID = "session_0";
|
|
31
|
+
var SESSION_ID_PREFIX = "session_";
|
|
32
|
+
var piMessageSchema = z.object({
|
|
33
|
+
role: z.string()
|
|
34
|
+
}).passthrough().transform((value) => value);
|
|
35
|
+
var piMessageEntrySchema = z.object({
|
|
36
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
37
|
+
type: z.literal("pi_message"),
|
|
38
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
39
|
+
message: piMessageSchema
|
|
40
|
+
});
|
|
41
|
+
var projectionResetEntrySchema = z.object({
|
|
42
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
43
|
+
type: z.literal("projection_reset"),
|
|
44
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
45
|
+
messages: z.array(piMessageSchema)
|
|
46
|
+
});
|
|
47
|
+
var mcpProviderConnectedEntrySchema = z.object({
|
|
48
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
49
|
+
type: z.literal("mcp_provider_connected"),
|
|
50
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
51
|
+
provider: z.string().min(1)
|
|
52
|
+
});
|
|
53
|
+
var authorizationKindSchema = z.union([
|
|
54
|
+
z.literal("plugin"),
|
|
55
|
+
z.literal("mcp")
|
|
56
|
+
]);
|
|
57
|
+
var authorizationRequestedEntrySchema = z.object({
|
|
58
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
59
|
+
type: z.literal("authorization_requested"),
|
|
60
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
61
|
+
createdAtMs: z.number().int().nonnegative(),
|
|
62
|
+
kind: authorizationKindSchema,
|
|
63
|
+
provider: z.string().min(1),
|
|
64
|
+
requesterId: z.string().min(1),
|
|
65
|
+
authorizationId: z.string().min(1),
|
|
66
|
+
delivery: z.union([
|
|
67
|
+
z.literal("private_link_sent"),
|
|
68
|
+
z.literal("private_link_reused")
|
|
69
|
+
])
|
|
70
|
+
});
|
|
71
|
+
var authorizationCompletedEntrySchema = z.object({
|
|
72
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
73
|
+
type: z.literal("authorization_completed"),
|
|
74
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
75
|
+
createdAtMs: z.number().int().nonnegative(),
|
|
76
|
+
kind: authorizationKindSchema,
|
|
77
|
+
provider: z.string().min(1),
|
|
78
|
+
requesterId: z.string().min(1),
|
|
79
|
+
authorizationId: z.string().min(1)
|
|
80
|
+
});
|
|
81
|
+
var sessionLogEntrySchema = z.discriminatedUnion("type", [
|
|
82
|
+
piMessageEntrySchema,
|
|
83
|
+
projectionResetEntrySchema,
|
|
84
|
+
mcpProviderConnectedEntrySchema,
|
|
85
|
+
authorizationRequestedEntrySchema,
|
|
86
|
+
authorizationCompletedEntrySchema
|
|
87
|
+
]);
|
|
88
|
+
function key(scope) {
|
|
89
|
+
const prefix = getChatConfig().state.keyPrefix;
|
|
90
|
+
return [
|
|
91
|
+
...prefix ? [prefix] : [],
|
|
92
|
+
AGENT_SESSION_LOG_PREFIX,
|
|
93
|
+
scope.conversationId
|
|
94
|
+
].join(":");
|
|
95
|
+
}
|
|
96
|
+
function rawKey(scope) {
|
|
97
|
+
return [AGENT_SESSION_LOG_PREFIX, scope.conversationId].join(":");
|
|
98
|
+
}
|
|
99
|
+
function normalizeMessageCount(value) {
|
|
100
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
101
|
+
}
|
|
102
|
+
function countMatchingPrefix(left, right) {
|
|
103
|
+
const limit = Math.min(left.length, right.length);
|
|
104
|
+
for (let index = 0; index < limit; index += 1) {
|
|
105
|
+
if (!isDeepStrictEqual(left[index], right[index])) {
|
|
106
|
+
return index;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return limit;
|
|
110
|
+
}
|
|
111
|
+
function entrySessionId(entry) {
|
|
112
|
+
return entry.sessionId ?? INITIAL_SESSION_ID;
|
|
113
|
+
}
|
|
114
|
+
function latestProjectionResetIndex(entries) {
|
|
115
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
116
|
+
if (entries[index]?.type === "projection_reset") {
|
|
117
|
+
return index;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return -1;
|
|
121
|
+
}
|
|
122
|
+
function currentSessionId(entries) {
|
|
123
|
+
const resetIndex = latestProjectionResetIndex(entries);
|
|
124
|
+
if (resetIndex < 0) {
|
|
125
|
+
return INITIAL_SESSION_ID;
|
|
126
|
+
}
|
|
127
|
+
return entrySessionId(entries[resetIndex]);
|
|
128
|
+
}
|
|
129
|
+
function nextSessionId(entries) {
|
|
130
|
+
const resetCount = entries.filter(
|
|
131
|
+
(entry) => entry.type === "projection_reset"
|
|
132
|
+
).length;
|
|
133
|
+
return `${SESSION_ID_PREFIX}${resetCount + 1}`;
|
|
134
|
+
}
|
|
135
|
+
function projectionEntries(entries, sessionId) {
|
|
136
|
+
if (sessionId) {
|
|
137
|
+
const sessionEntries = [];
|
|
138
|
+
let started = false;
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
const entryId = entrySessionId(entry);
|
|
141
|
+
if (!started) {
|
|
142
|
+
if (entryId !== sessionId) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
started = true;
|
|
146
|
+
} else if (entry.type === "projection_reset" && entryId !== sessionId) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
if (entryId === sessionId) {
|
|
150
|
+
sessionEntries.push(entry);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return sessionEntries;
|
|
154
|
+
}
|
|
155
|
+
const resetIndex = latestProjectionResetIndex(entries);
|
|
156
|
+
const startIndex = resetIndex < 0 ? 0 : resetIndex;
|
|
157
|
+
const currentId = resetIndex < 0 ? INITIAL_SESSION_ID : entrySessionId(entries[resetIndex]);
|
|
158
|
+
return entries.slice(startIndex).filter((entry) => entrySessionId(entry) === currentId);
|
|
159
|
+
}
|
|
160
|
+
function piEntry(message, sessionId) {
|
|
161
|
+
return {
|
|
162
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
163
|
+
type: "pi_message",
|
|
164
|
+
sessionId,
|
|
165
|
+
message
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function resetEntry(messages, sessionId) {
|
|
169
|
+
return {
|
|
170
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
171
|
+
type: "projection_reset",
|
|
172
|
+
sessionId,
|
|
173
|
+
messages
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function mcpProviderConnectedEntry(provider, sessionId) {
|
|
177
|
+
return {
|
|
178
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
179
|
+
type: "mcp_provider_connected",
|
|
180
|
+
sessionId,
|
|
181
|
+
provider
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function authorizationObservationMessage(entry) {
|
|
185
|
+
const label = entry.kind === "mcp" ? "MCP authorization" : "Authorization";
|
|
186
|
+
return {
|
|
187
|
+
role: "user",
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `${label} completed for provider "${entry.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
timestamp: entry.createdAtMs
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function authorizationRequestedEntry(args) {
|
|
198
|
+
return {
|
|
199
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
200
|
+
type: "authorization_requested",
|
|
201
|
+
sessionId: args.sessionId,
|
|
202
|
+
createdAtMs: args.createdAtMs,
|
|
203
|
+
kind: args.kind,
|
|
204
|
+
provider: args.provider,
|
|
205
|
+
requesterId: args.requesterId,
|
|
206
|
+
authorizationId: args.authorizationId,
|
|
207
|
+
delivery: args.delivery
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function authorizationCompletedEntry(args) {
|
|
211
|
+
return {
|
|
212
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
213
|
+
type: "authorization_completed",
|
|
214
|
+
sessionId: args.sessionId,
|
|
215
|
+
createdAtMs: args.createdAtMs,
|
|
216
|
+
kind: args.kind,
|
|
217
|
+
provider: args.provider,
|
|
218
|
+
requesterId: args.requesterId,
|
|
219
|
+
authorizationId: args.authorizationId
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function decode(value) {
|
|
223
|
+
if (typeof value === "string") {
|
|
224
|
+
return decode(JSON.parse(value));
|
|
225
|
+
}
|
|
226
|
+
const parsed = sessionLogEntrySchema.safeParse(value);
|
|
227
|
+
if (parsed.success) {
|
|
228
|
+
return parsed.data;
|
|
229
|
+
}
|
|
230
|
+
return piEntry(piMessageSchema.parse(value), INITIAL_SESSION_ID);
|
|
231
|
+
}
|
|
232
|
+
function project(entries, sessionId) {
|
|
233
|
+
let messages = [];
|
|
234
|
+
for (const entry of projectionEntries(entries, sessionId)) {
|
|
235
|
+
if (entry.type === "pi_message") {
|
|
236
|
+
messages.push(entry.message);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (entry.type === "authorization_completed") {
|
|
240
|
+
messages.push(authorizationObservationMessage(entry));
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (entry.type === "mcp_provider_connected" || entry.type === "authorization_requested") {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
messages = [...entry.messages];
|
|
247
|
+
}
|
|
248
|
+
return messages;
|
|
249
|
+
}
|
|
250
|
+
function connectedMcpProviders(entries, sessionId) {
|
|
251
|
+
const providers = /* @__PURE__ */ new Set();
|
|
252
|
+
for (const entry of projectionEntries(entries, sessionId)) {
|
|
253
|
+
if (entry.type === "mcp_provider_connected") {
|
|
254
|
+
providers.add(entry.provider);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
258
|
+
}
|
|
259
|
+
function commitEntries(existingMessages, nextMessages, sessionId, entries) {
|
|
260
|
+
const matchingPrefix = countMatchingPrefix(existingMessages, nextMessages);
|
|
261
|
+
if (matchingPrefix === existingMessages.length) {
|
|
262
|
+
return nextMessages.slice(matchingPrefix).map((message) => piEntry(message, sessionId));
|
|
263
|
+
}
|
|
264
|
+
return [resetEntry(nextMessages, nextSessionId(entries))];
|
|
265
|
+
}
|
|
266
|
+
function redisStore(redisStateAdapter) {
|
|
267
|
+
const client = redisStateAdapter.getClient();
|
|
268
|
+
return {
|
|
269
|
+
async append({ entries, scope, ttlMs }) {
|
|
270
|
+
const listKey = key(scope);
|
|
271
|
+
if (entries.length > 0) {
|
|
272
|
+
await client.rPush(
|
|
273
|
+
listKey,
|
|
274
|
+
entries.map((entry) => JSON.stringify(entry))
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
await client.pExpire(listKey, Math.max(1, ttlMs));
|
|
278
|
+
},
|
|
279
|
+
async read(scope) {
|
|
280
|
+
const values = await client.lRange(key(scope), 0, -1);
|
|
281
|
+
return values.map(decode);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function stateStore() {
|
|
286
|
+
const stateAdapter = getStateAdapter();
|
|
287
|
+
return {
|
|
288
|
+
async append({ entries, scope, ttlMs }) {
|
|
289
|
+
const listKey = rawKey(scope);
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
await stateAdapter.appendToList(listKey, entry, {
|
|
292
|
+
ttlMs: Math.max(1, ttlMs)
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
async read(scope) {
|
|
297
|
+
const values = await stateAdapter.getList(rawKey(scope));
|
|
298
|
+
return values.map(decode);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async function defaultStore() {
|
|
303
|
+
const { redisStateAdapter, stateAdapter } = await getConnectedStateContext();
|
|
304
|
+
if (redisStateAdapter) {
|
|
305
|
+
return redisStore(redisStateAdapter);
|
|
306
|
+
}
|
|
307
|
+
await stateAdapter.connect();
|
|
308
|
+
return stateStore();
|
|
309
|
+
}
|
|
310
|
+
async function loadEntries(args) {
|
|
311
|
+
const store = args.store ?? await defaultStore();
|
|
312
|
+
return await store.read(args);
|
|
313
|
+
}
|
|
314
|
+
async function loadMessages(args) {
|
|
315
|
+
const messageCount = normalizeMessageCount(args.messageCount);
|
|
316
|
+
if (messageCount === 0) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
const store = args.store ?? await defaultStore();
|
|
320
|
+
const messages = project(await store.read(args), args.sessionId);
|
|
321
|
+
return messages.length >= messageCount ? messages.slice(0, messageCount) : void 0;
|
|
322
|
+
}
|
|
323
|
+
async function loadProjection(args) {
|
|
324
|
+
const store = args.store ?? await defaultStore();
|
|
325
|
+
return project(await store.read(args), args.sessionId);
|
|
326
|
+
}
|
|
327
|
+
async function loadConnectedMcpProviders(args) {
|
|
328
|
+
return connectedMcpProviders(await loadEntries(args));
|
|
329
|
+
}
|
|
330
|
+
async function recordMcpProviderConnected(args) {
|
|
331
|
+
const store = args.store ?? await defaultStore();
|
|
332
|
+
const entries = await store.read(args);
|
|
333
|
+
const sessionId = currentSessionId(entries);
|
|
334
|
+
if (connectedMcpProviders(entries).includes(args.provider)) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
await store.append({
|
|
338
|
+
scope: args,
|
|
339
|
+
entries: [mcpProviderConnectedEntry(args.provider, sessionId)],
|
|
340
|
+
ttlMs: args.ttlMs
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async function recordAuthorizationRequested(args) {
|
|
344
|
+
const store = args.store ?? await defaultStore();
|
|
345
|
+
const entries = await store.read(args);
|
|
346
|
+
const sessionId = currentSessionId(entries);
|
|
347
|
+
if (projectionEntries(entries).some(
|
|
348
|
+
(entry) => entry.type === "authorization_requested" && entry.authorizationId === args.authorizationId
|
|
349
|
+
)) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
await store.append({
|
|
353
|
+
scope: args,
|
|
354
|
+
entries: [
|
|
355
|
+
authorizationRequestedEntry({
|
|
356
|
+
createdAtMs: Date.now(),
|
|
357
|
+
kind: args.kind,
|
|
358
|
+
sessionId,
|
|
359
|
+
provider: args.provider,
|
|
360
|
+
requesterId: args.requesterId,
|
|
361
|
+
authorizationId: args.authorizationId,
|
|
362
|
+
delivery: args.delivery
|
|
363
|
+
})
|
|
364
|
+
],
|
|
365
|
+
ttlMs: args.ttlMs
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
async function recordAuthorizationCompleted(args) {
|
|
369
|
+
const store = args.store ?? await defaultStore();
|
|
370
|
+
const entries = await store.read(args);
|
|
371
|
+
const sessionId = currentSessionId(entries);
|
|
372
|
+
if (projectionEntries(entries).some(
|
|
373
|
+
(entry) => entry.type === "authorization_completed" && entry.authorizationId === args.authorizationId
|
|
374
|
+
)) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
await store.append({
|
|
378
|
+
scope: args,
|
|
379
|
+
entries: [
|
|
380
|
+
authorizationCompletedEntry({
|
|
381
|
+
createdAtMs: Date.now(),
|
|
382
|
+
kind: args.kind,
|
|
383
|
+
sessionId,
|
|
384
|
+
provider: args.provider,
|
|
385
|
+
requesterId: args.requesterId,
|
|
386
|
+
authorizationId: args.authorizationId
|
|
387
|
+
})
|
|
388
|
+
],
|
|
389
|
+
ttlMs: args.ttlMs
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
async function commitMessages(args) {
|
|
393
|
+
const store = args.store ?? await defaultStore();
|
|
394
|
+
const entries = await store.read(args);
|
|
395
|
+
const existingMessages = project(entries);
|
|
396
|
+
const currentId = currentSessionId(entries);
|
|
397
|
+
const nextEntries = commitEntries(
|
|
398
|
+
existingMessages,
|
|
399
|
+
args.messages,
|
|
400
|
+
currentId,
|
|
401
|
+
entries
|
|
402
|
+
);
|
|
403
|
+
await store.append({
|
|
404
|
+
scope: args,
|
|
405
|
+
entries: nextEntries,
|
|
406
|
+
ttlMs: args.ttlMs
|
|
407
|
+
});
|
|
408
|
+
return {
|
|
409
|
+
sessionId: nextEntries.find((entry) => entry.type === "projection_reset")?.sessionId ?? currentId
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/chat/state/turn-session.ts
|
|
414
|
+
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
415
|
+
var AGENT_TURN_SESSION_INDEX_KEY = `${AGENT_TURN_SESSION_PREFIX}:index`;
|
|
416
|
+
var AGENT_TURN_SESSION_INDEX_MAX_LENGTH = 5e3;
|
|
417
|
+
var AGENT_TURN_SESSION_TTL_MS = THREAD_STATE_TTL_MS;
|
|
418
|
+
function agentTurnSessionKey(conversationId, sessionId) {
|
|
419
|
+
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
420
|
+
}
|
|
421
|
+
function agentTurnSessionConversationIndexKey(conversationId) {
|
|
422
|
+
return `${AGENT_TURN_SESSION_PREFIX}:conversation:${conversationId}:index`;
|
|
423
|
+
}
|
|
424
|
+
function toFiniteNonNegativeNumber(value) {
|
|
425
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
426
|
+
}
|
|
427
|
+
function parseAgentTurnUsage(value) {
|
|
428
|
+
if (!isRecord(value)) {
|
|
429
|
+
return void 0;
|
|
430
|
+
}
|
|
431
|
+
const usage = {};
|
|
432
|
+
for (const field of [
|
|
433
|
+
"inputTokens",
|
|
434
|
+
"outputTokens",
|
|
435
|
+
"cachedInputTokens",
|
|
436
|
+
"cacheCreationTokens",
|
|
437
|
+
"totalTokens"
|
|
438
|
+
]) {
|
|
439
|
+
const count = toFiniteNonNegativeNumber(value[field]);
|
|
440
|
+
if (count !== void 0) {
|
|
441
|
+
usage[field] = count;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
445
|
+
}
|
|
446
|
+
function parseAgentTurnRequester(value) {
|
|
447
|
+
if (!isRecord(value)) {
|
|
448
|
+
return void 0;
|
|
449
|
+
}
|
|
450
|
+
const requester = {};
|
|
451
|
+
for (const field of [
|
|
452
|
+
"email",
|
|
453
|
+
"fullName",
|
|
454
|
+
"slackUserId",
|
|
455
|
+
"slackUserName"
|
|
456
|
+
]) {
|
|
457
|
+
if (typeof value[field] === "string" && value[field].trim()) {
|
|
458
|
+
requester[field] = value[field].trim();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return Object.keys(requester).length > 0 ? requester : void 0;
|
|
462
|
+
}
|
|
463
|
+
function parseStoredRecord(value) {
|
|
464
|
+
if (isRecord(value)) {
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
if (typeof value !== "string") {
|
|
468
|
+
return void 0;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
const parsed = JSON.parse(value);
|
|
472
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
473
|
+
} catch {
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function parseAgentTurnSessionStatus(parsed) {
|
|
478
|
+
const status = parsed.state;
|
|
479
|
+
if (status === "running" || status === "awaiting_resume" || status === "completed" || status === "failed" || status === "abandoned") {
|
|
480
|
+
return status;
|
|
481
|
+
}
|
|
482
|
+
return void 0;
|
|
483
|
+
}
|
|
484
|
+
function parseAgentTurnSessionFields(parsed) {
|
|
485
|
+
const status = parseAgentTurnSessionStatus(parsed);
|
|
486
|
+
if (!status) {
|
|
487
|
+
return void 0;
|
|
488
|
+
}
|
|
489
|
+
const channelName = typeof parsed.channelName === "string" && parsed.channelName.trim() ? parsed.channelName.trim() : void 0;
|
|
490
|
+
const conversationTitle = typeof parsed.conversationTitle === "string" && parsed.conversationTitle.trim() ? parsed.conversationTitle.trim() : void 0;
|
|
491
|
+
const conversationId = parsed.conversationId;
|
|
492
|
+
const sessionId = parsed.sessionId;
|
|
493
|
+
const sliceId = toFiniteNonNegativeNumber(parsed.sliceId);
|
|
494
|
+
const version = toFiniteNonNegativeNumber(parsed.version);
|
|
495
|
+
const updatedAtMs = toFiniteNonNegativeNumber(parsed.updatedAtMs);
|
|
496
|
+
const cumulativeDurationMs = toFiniteNonNegativeNumber(
|
|
497
|
+
parsed.cumulativeDurationMs
|
|
498
|
+
);
|
|
499
|
+
const cumulativeUsage = parseAgentTurnUsage(parsed.cumulativeUsage);
|
|
500
|
+
const lastProgressAtMs = toFiniteNonNegativeNumber(parsed.lastProgressAtMs);
|
|
501
|
+
const logSessionId = typeof parsed.logSessionId === "string" ? parsed.logSessionId : void 0;
|
|
502
|
+
const requester = parseAgentTurnRequester(parsed.requester);
|
|
503
|
+
const startedAtMs = toFiniteNonNegativeNumber(parsed.startedAtMs);
|
|
504
|
+
if (typeof conversationId !== "string" || typeof sessionId !== "string" || sliceId === void 0 || version === void 0 || updatedAtMs === void 0) {
|
|
505
|
+
return void 0;
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
version,
|
|
509
|
+
...channelName ? { channelName } : {},
|
|
510
|
+
...conversationTitle ? { conversationTitle } : {},
|
|
511
|
+
conversationId,
|
|
512
|
+
sessionId,
|
|
513
|
+
sliceId,
|
|
514
|
+
state: status,
|
|
515
|
+
startedAtMs: startedAtMs ?? updatedAtMs,
|
|
516
|
+
lastProgressAtMs: lastProgressAtMs ?? updatedAtMs,
|
|
517
|
+
updatedAtMs,
|
|
518
|
+
...logSessionId ? { logSessionId } : {},
|
|
519
|
+
...cumulativeDurationMs !== void 0 ? { cumulativeDurationMs } : {},
|
|
520
|
+
...cumulativeUsage ? { cumulativeUsage } : {},
|
|
521
|
+
...requester ? { requester } : {},
|
|
522
|
+
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
523
|
+
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
524
|
+
(value) => typeof value === "string"
|
|
525
|
+
)
|
|
526
|
+
} : {},
|
|
527
|
+
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
528
|
+
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
529
|
+
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {},
|
|
530
|
+
...typeof parsed.traceId === "string" ? { traceId: parsed.traceId } : {}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function parseAgentTurnSessionRecord(value) {
|
|
534
|
+
const parsed = parseStoredRecord(value);
|
|
535
|
+
if (!parsed) {
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
const fields = parseAgentTurnSessionFields(parsed);
|
|
539
|
+
const committedMessageCount = toFiniteNonNegativeNumber(
|
|
540
|
+
parsed.committedMessageCount
|
|
541
|
+
);
|
|
542
|
+
if (!fields || committedMessageCount === void 0) {
|
|
543
|
+
return void 0;
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
...fields,
|
|
547
|
+
committedMessageCount
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function parseAgentTurnSessionSummary(value) {
|
|
551
|
+
const stored = parseStoredRecord(value);
|
|
552
|
+
if (!stored) {
|
|
553
|
+
return void 0;
|
|
554
|
+
}
|
|
555
|
+
const parsed = parseAgentTurnSessionFields(stored);
|
|
556
|
+
if (!parsed) {
|
|
557
|
+
return void 0;
|
|
558
|
+
}
|
|
559
|
+
const {
|
|
560
|
+
errorMessage: _errorMessage,
|
|
561
|
+
logSessionId: _logSessionId,
|
|
562
|
+
...summary
|
|
563
|
+
} = parsed;
|
|
564
|
+
return summary;
|
|
565
|
+
}
|
|
566
|
+
async function appendAgentTurnSessionSummary(summary, ttlMs) {
|
|
567
|
+
const stateAdapter = getStateAdapter();
|
|
568
|
+
await Promise.all([
|
|
569
|
+
stateAdapter.appendToList(AGENT_TURN_SESSION_INDEX_KEY, summary, {
|
|
570
|
+
maxLength: AGENT_TURN_SESSION_INDEX_MAX_LENGTH,
|
|
571
|
+
ttlMs
|
|
572
|
+
}),
|
|
573
|
+
stateAdapter.appendToList(
|
|
574
|
+
agentTurnSessionConversationIndexKey(summary.conversationId),
|
|
575
|
+
summary,
|
|
576
|
+
{ ttlMs }
|
|
577
|
+
)
|
|
578
|
+
]);
|
|
579
|
+
}
|
|
580
|
+
function materializePiMessages(committedMessageCount, includeProjectionTail, sessionMessages, sessionProjection) {
|
|
581
|
+
if (committedMessageCount === 0) {
|
|
582
|
+
return sessionProjection;
|
|
583
|
+
}
|
|
584
|
+
if (includeProjectionTail && sessionProjection.length >= committedMessageCount) {
|
|
585
|
+
return sessionProjection;
|
|
586
|
+
}
|
|
587
|
+
if (sessionMessages) {
|
|
588
|
+
return sessionMessages;
|
|
589
|
+
}
|
|
590
|
+
if (sessionProjection.length >= committedMessageCount) {
|
|
591
|
+
return sessionProjection.slice(0, committedMessageCount);
|
|
592
|
+
}
|
|
593
|
+
return void 0;
|
|
594
|
+
}
|
|
595
|
+
function materializeAgentTurnSessionRecord(stored, piMessages) {
|
|
596
|
+
return {
|
|
597
|
+
version: stored.version,
|
|
598
|
+
...stored.channelName ? { channelName: stored.channelName } : {},
|
|
599
|
+
...stored.conversationTitle ? { conversationTitle: stored.conversationTitle } : {},
|
|
600
|
+
conversationId: stored.conversationId,
|
|
601
|
+
sessionId: stored.sessionId,
|
|
602
|
+
sliceId: stored.sliceId,
|
|
603
|
+
state: stored.state,
|
|
604
|
+
startedAtMs: stored.startedAtMs,
|
|
605
|
+
lastProgressAtMs: stored.lastProgressAtMs,
|
|
606
|
+
updatedAtMs: stored.updatedAtMs,
|
|
607
|
+
piMessages,
|
|
608
|
+
...stored.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: stored.cumulativeDurationMs } : {},
|
|
609
|
+
...stored.cumulativeUsage ? { cumulativeUsage: stored.cumulativeUsage } : {},
|
|
610
|
+
...stored.resumeReason ? { resumeReason: stored.resumeReason } : {},
|
|
611
|
+
...stored.errorMessage ? { errorMessage: stored.errorMessage } : {},
|
|
612
|
+
...stored.loadedSkillNames ? { loadedSkillNames: stored.loadedSkillNames } : {},
|
|
613
|
+
...stored.requester ? { requester: stored.requester } : {},
|
|
614
|
+
...stored.resumedFromSliceId !== void 0 ? { resumedFromSliceId: stored.resumedFromSliceId } : {},
|
|
615
|
+
...stored.traceId ? { traceId: stored.traceId } : {}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
async function getStoredAgentTurnSessionRecord(conversationId, sessionId) {
|
|
619
|
+
const stateAdapter = getStateAdapter();
|
|
620
|
+
await stateAdapter.connect();
|
|
621
|
+
const value = await stateAdapter.get(
|
|
622
|
+
agentTurnSessionKey(conversationId, sessionId)
|
|
623
|
+
);
|
|
624
|
+
return parseAgentTurnSessionRecord(value);
|
|
625
|
+
}
|
|
626
|
+
async function getAgentTurnSessionRecord(conversationId, sessionId) {
|
|
627
|
+
const parsed = await getStoredAgentTurnSessionRecord(
|
|
628
|
+
conversationId,
|
|
629
|
+
sessionId
|
|
630
|
+
);
|
|
631
|
+
if (!parsed) {
|
|
632
|
+
return void 0;
|
|
633
|
+
}
|
|
634
|
+
const sessionMessages = await loadMessages({
|
|
635
|
+
conversationId,
|
|
636
|
+
messageCount: parsed.committedMessageCount,
|
|
637
|
+
...parsed.logSessionId ? { sessionId: parsed.logSessionId } : {}
|
|
638
|
+
});
|
|
639
|
+
const sessionProjection = await loadProjection({
|
|
640
|
+
conversationId,
|
|
641
|
+
...parsed.logSessionId ? { sessionId: parsed.logSessionId } : {}
|
|
642
|
+
});
|
|
643
|
+
const piMessages = materializePiMessages(
|
|
644
|
+
parsed.committedMessageCount,
|
|
645
|
+
parsed.state === "running" || parsed.state === "awaiting_resume",
|
|
646
|
+
sessionMessages,
|
|
647
|
+
sessionProjection
|
|
648
|
+
);
|
|
649
|
+
if (!piMessages) {
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
return materializeAgentTurnSessionRecord(parsed, piMessages);
|
|
653
|
+
}
|
|
654
|
+
function buildStoredRecord(args) {
|
|
655
|
+
const nowMs = Date.now();
|
|
656
|
+
return {
|
|
657
|
+
version: (args.previousVersion ?? 0) + 1,
|
|
658
|
+
...args.channelName ? { channelName: args.channelName } : {},
|
|
659
|
+
...args.conversationTitle ? { conversationTitle: args.conversationTitle } : {},
|
|
660
|
+
conversationId: args.conversationId,
|
|
661
|
+
sessionId: args.sessionId,
|
|
662
|
+
sliceId: args.sliceId,
|
|
663
|
+
state: args.state,
|
|
664
|
+
startedAtMs: args.startedAtMs ?? nowMs,
|
|
665
|
+
lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
|
|
666
|
+
updatedAtMs: nowMs,
|
|
667
|
+
committedMessageCount: args.committedMessageCount,
|
|
668
|
+
...args.logSessionId ? { logSessionId: args.logSessionId } : {},
|
|
669
|
+
...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
|
|
670
|
+
cumulativeDurationMs: Math.max(
|
|
671
|
+
0,
|
|
672
|
+
Math.floor(args.cumulativeDurationMs)
|
|
673
|
+
)
|
|
674
|
+
} : {},
|
|
675
|
+
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
676
|
+
...args.requester ? { requester: args.requester } : {},
|
|
677
|
+
...Array.isArray(args.loadedSkillNames) ? {
|
|
678
|
+
loadedSkillNames: args.loadedSkillNames.filter(
|
|
679
|
+
(value) => typeof value === "string"
|
|
680
|
+
)
|
|
681
|
+
} : {},
|
|
682
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
683
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
684
|
+
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {},
|
|
685
|
+
...args.traceId ? { traceId: args.traceId } : {}
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async function setStoredRecord(args) {
|
|
689
|
+
const stateAdapter = getStateAdapter();
|
|
690
|
+
await stateAdapter.connect();
|
|
691
|
+
await stateAdapter.set(
|
|
692
|
+
agentTurnSessionKey(args.record.conversationId, args.record.sessionId),
|
|
693
|
+
args.record,
|
|
694
|
+
args.ttlMs
|
|
695
|
+
);
|
|
696
|
+
const {
|
|
697
|
+
committedMessageCount: _committedMessageCount,
|
|
698
|
+
errorMessage: _errorMessage,
|
|
699
|
+
logSessionId: _logSessionId,
|
|
700
|
+
...summary
|
|
701
|
+
} = args.record;
|
|
702
|
+
await appendAgentTurnSessionSummary(summary, args.ttlMs);
|
|
703
|
+
return materializeAgentTurnSessionRecord(args.record, [...args.piMessages]);
|
|
704
|
+
}
|
|
705
|
+
async function updateAgentTurnSessionState(args) {
|
|
706
|
+
const parsed = await getStoredAgentTurnSessionRecord(
|
|
707
|
+
args.existing.conversationId,
|
|
708
|
+
args.existing.sessionId
|
|
709
|
+
);
|
|
710
|
+
if (!parsed || parsed.version !== args.existing.version) {
|
|
711
|
+
return void 0;
|
|
712
|
+
}
|
|
713
|
+
return await setStoredRecord({
|
|
714
|
+
piMessages: args.existing.piMessages,
|
|
715
|
+
ttlMs: AGENT_TURN_SESSION_TTL_MS,
|
|
716
|
+
record: buildStoredRecord({
|
|
717
|
+
conversationId: args.existing.conversationId,
|
|
718
|
+
sessionId: args.existing.sessionId,
|
|
719
|
+
sliceId: args.existing.sliceId,
|
|
720
|
+
state: args.state,
|
|
721
|
+
committedMessageCount: parsed.committedMessageCount,
|
|
722
|
+
...parsed.channelName ? { channelName: parsed.channelName } : {},
|
|
723
|
+
...parsed.conversationTitle ? { conversationTitle: parsed.conversationTitle } : {},
|
|
724
|
+
startedAtMs: parsed.startedAtMs,
|
|
725
|
+
lastProgressAtMs: parsed.lastProgressAtMs,
|
|
726
|
+
previousVersion: parsed.version,
|
|
727
|
+
...parsed.logSessionId ? { logSessionId: parsed.logSessionId } : {},
|
|
728
|
+
...args.existing.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: args.existing.cumulativeDurationMs } : {},
|
|
729
|
+
...args.existing.cumulativeUsage ? { cumulativeUsage: args.existing.cumulativeUsage } : {},
|
|
730
|
+
...args.existing.loadedSkillNames ? { loadedSkillNames: args.existing.loadedSkillNames } : {},
|
|
731
|
+
...args.existing.requester ? { requester: args.existing.requester } : {},
|
|
732
|
+
...args.existing.resumeReason ? { resumeReason: args.existing.resumeReason } : {},
|
|
733
|
+
...args.existing.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.existing.resumedFromSliceId } : {},
|
|
734
|
+
...args.existing.traceId ? { traceId: args.existing.traceId } : {},
|
|
735
|
+
...args.errorMessage ?? args.existing.errorMessage ? { errorMessage: args.errorMessage ?? args.existing.errorMessage } : {}
|
|
736
|
+
})
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
async function upsertAgentTurnSessionRecord(args) {
|
|
740
|
+
const existingRecord = await getStoredAgentTurnSessionRecord(
|
|
741
|
+
args.conversationId,
|
|
742
|
+
args.sessionId
|
|
743
|
+
);
|
|
744
|
+
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
745
|
+
const commit = await commitMessages({
|
|
746
|
+
conversationId: args.conversationId,
|
|
747
|
+
messages: args.piMessages,
|
|
748
|
+
ttlMs
|
|
749
|
+
});
|
|
750
|
+
return await setStoredRecord({
|
|
751
|
+
piMessages: args.piMessages,
|
|
752
|
+
ttlMs,
|
|
753
|
+
record: buildStoredRecord({
|
|
754
|
+
...args.channelName ?? existingRecord?.channelName ? { channelName: args.channelName ?? existingRecord?.channelName } : {},
|
|
755
|
+
...args.conversationTitle ?? existingRecord?.conversationTitle ? {
|
|
756
|
+
conversationTitle: args.conversationTitle ?? existingRecord?.conversationTitle
|
|
757
|
+
} : {},
|
|
758
|
+
conversationId: args.conversationId,
|
|
759
|
+
sessionId: args.sessionId,
|
|
760
|
+
sliceId: args.sliceId,
|
|
761
|
+
state: args.state,
|
|
762
|
+
...existingRecord?.startedAtMs !== void 0 ? { startedAtMs: existingRecord.startedAtMs } : {},
|
|
763
|
+
...args.lastProgressAtMs !== void 0 ? { lastProgressAtMs: args.lastProgressAtMs } : {},
|
|
764
|
+
committedMessageCount: args.piMessages.length,
|
|
765
|
+
logSessionId: commit.sessionId,
|
|
766
|
+
previousVersion: existingRecord?.version,
|
|
767
|
+
...args.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: args.cumulativeDurationMs } : {},
|
|
768
|
+
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
769
|
+
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
770
|
+
...args.requester ?? existingRecord?.requester ? { requester: args.requester ?? existingRecord?.requester } : {},
|
|
771
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
772
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
773
|
+
...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
|
|
774
|
+
...args.traceId ?? existingRecord?.traceId ? { traceId: args.traceId ?? existingRecord?.traceId } : {}
|
|
775
|
+
})
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
async function recordAgentTurnSessionSummary(args) {
|
|
779
|
+
const existing = await getStoredAgentTurnSessionRecord(
|
|
780
|
+
args.conversationId,
|
|
781
|
+
args.sessionId
|
|
782
|
+
);
|
|
783
|
+
const nowMs = Date.now();
|
|
784
|
+
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
785
|
+
await appendAgentTurnSessionSummary(
|
|
786
|
+
{
|
|
787
|
+
version: existing?.version ?? 0,
|
|
788
|
+
...args.channelName ?? existing?.channelName ? { channelName: args.channelName ?? existing?.channelName } : {},
|
|
789
|
+
...args.conversationTitle ?? existing?.conversationTitle ? {
|
|
790
|
+
conversationTitle: args.conversationTitle ?? existing?.conversationTitle
|
|
791
|
+
} : {},
|
|
792
|
+
conversationId: args.conversationId,
|
|
793
|
+
sessionId: args.sessionId,
|
|
794
|
+
sliceId: args.sliceId,
|
|
795
|
+
startedAtMs: existing?.startedAtMs ?? args.startedAtMs ?? nowMs,
|
|
796
|
+
lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
|
|
797
|
+
state: args.state,
|
|
798
|
+
updatedAtMs: nowMs,
|
|
799
|
+
...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
|
|
800
|
+
cumulativeDurationMs: Math.max(
|
|
801
|
+
0,
|
|
802
|
+
Math.floor(args.cumulativeDurationMs)
|
|
803
|
+
)
|
|
804
|
+
} : existing?.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: existing.cumulativeDurationMs } : {},
|
|
805
|
+
...args.cumulativeUsage ?? existing?.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage ?? existing?.cumulativeUsage } : {},
|
|
806
|
+
...args.requester ?? existing?.requester ? { requester: args.requester ?? existing?.requester } : {},
|
|
807
|
+
...Array.isArray(args.loadedSkillNames) ? {
|
|
808
|
+
loadedSkillNames: args.loadedSkillNames.filter(
|
|
809
|
+
(value) => typeof value === "string"
|
|
810
|
+
)
|
|
811
|
+
} : existing?.loadedSkillNames ? { loadedSkillNames: existing.loadedSkillNames } : {},
|
|
812
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
813
|
+
...args.traceId ?? existing?.traceId ? { traceId: args.traceId ?? existing?.traceId } : {}
|
|
814
|
+
},
|
|
815
|
+
ttlMs
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
async function readAgentTurnSessionSummariesFromIndex(key2) {
|
|
819
|
+
const stateAdapter = getStateAdapter();
|
|
820
|
+
await stateAdapter.connect();
|
|
821
|
+
const values = await stateAdapter.getList(key2);
|
|
822
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
823
|
+
for (const value of [...values].reverse()) {
|
|
824
|
+
const summary = parseAgentTurnSessionSummary(value);
|
|
825
|
+
if (!summary) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const key3 = `${summary.conversationId}:${summary.sessionId}`;
|
|
829
|
+
if (!summaries.has(key3)) {
|
|
830
|
+
summaries.set(key3, summary);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return [...summaries.values()].sort(
|
|
834
|
+
(left, right) => right.updatedAtMs - left.updatedAtMs
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
async function listAgentTurnSessionSummaries(limit = 50) {
|
|
838
|
+
return (await readAgentTurnSessionSummariesFromIndex(AGENT_TURN_SESSION_INDEX_KEY)).slice(0, Math.max(0, Math.floor(limit)));
|
|
839
|
+
}
|
|
840
|
+
async function listAgentTurnSessionSummariesForConversation(conversationId) {
|
|
841
|
+
const summaries = await readAgentTurnSessionSummariesFromIndex(
|
|
842
|
+
agentTurnSessionConversationIndexKey(conversationId)
|
|
843
|
+
);
|
|
844
|
+
if (summaries.length > 0) {
|
|
845
|
+
return summaries;
|
|
846
|
+
}
|
|
847
|
+
return (await readAgentTurnSessionSummariesFromIndex(AGENT_TURN_SESSION_INDEX_KEY)).filter((summary) => summary.conversationId === conversationId);
|
|
848
|
+
}
|
|
849
|
+
async function abandonAgentTurnSessionRecord(args) {
|
|
850
|
+
const existing = await getAgentTurnSessionRecord(
|
|
851
|
+
args.conversationId,
|
|
852
|
+
args.sessionId
|
|
853
|
+
);
|
|
854
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned") {
|
|
855
|
+
return void 0;
|
|
856
|
+
}
|
|
857
|
+
return await updateAgentTurnSessionState({
|
|
858
|
+
existing,
|
|
859
|
+
state: "abandoned",
|
|
860
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
async function failAgentTurnSessionRecord(args) {
|
|
864
|
+
const existing = await getAgentTurnSessionRecord(
|
|
865
|
+
args.conversationId,
|
|
866
|
+
args.sessionId
|
|
867
|
+
);
|
|
868
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
|
|
869
|
+
return void 0;
|
|
870
|
+
}
|
|
871
|
+
return await updateAgentTurnSessionState({
|
|
872
|
+
existing,
|
|
873
|
+
state: "failed",
|
|
874
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/chat/sentry-links.ts
|
|
879
|
+
function getSentryOrgSlug() {
|
|
880
|
+
const slug = process.env.SENTRY_ORG_SLUG?.trim();
|
|
881
|
+
return slug || void 0;
|
|
882
|
+
}
|
|
883
|
+
function isSentrySaasDsnHost(host) {
|
|
884
|
+
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
885
|
+
}
|
|
886
|
+
function buildSentryWebBaseUrl(dsn) {
|
|
887
|
+
if (isSentrySaasDsnHost(dsn.host)) {
|
|
888
|
+
return "https://sentry.io";
|
|
889
|
+
}
|
|
890
|
+
const port = dsn.port ? `:${dsn.port}` : "";
|
|
891
|
+
const path = dsn.path ? `/${dsn.path}` : "";
|
|
892
|
+
return `${dsn.protocol}://${dsn.host}${port}${path}`;
|
|
893
|
+
}
|
|
894
|
+
function buildSentryConversationUrl(conversationId) {
|
|
895
|
+
const client = sentry_exports.getClient();
|
|
896
|
+
const dsn = client?.getDsn();
|
|
897
|
+
if (!dsn?.host || !dsn.projectId) {
|
|
898
|
+
return void 0;
|
|
899
|
+
}
|
|
900
|
+
const orgSlug = getSentryOrgSlug();
|
|
901
|
+
if (!orgSlug) {
|
|
902
|
+
return void 0;
|
|
903
|
+
}
|
|
904
|
+
const encodedId = encodeURIComponent(conversationId);
|
|
905
|
+
const params = new URLSearchParams();
|
|
906
|
+
params.set("project", dsn.projectId);
|
|
907
|
+
const path = `explore/conversations/${encodedId}/?${params.toString()}`;
|
|
908
|
+
if (isSentrySaasDsnHost(dsn.host)) {
|
|
909
|
+
return `https://${orgSlug}.sentry.io/${path}`;
|
|
910
|
+
}
|
|
911
|
+
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path}`;
|
|
912
|
+
}
|
|
913
|
+
function buildSentryTraceUrl(traceId) {
|
|
914
|
+
const client = sentry_exports.getClient();
|
|
915
|
+
const dsn = client?.getDsn();
|
|
916
|
+
if (!dsn?.host || !dsn.projectId) {
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
919
|
+
const orgSlug = getSentryOrgSlug();
|
|
920
|
+
if (!orgSlug) {
|
|
921
|
+
return void 0;
|
|
922
|
+
}
|
|
923
|
+
const encodedTraceId = encodeURIComponent(traceId);
|
|
924
|
+
const params = new URLSearchParams();
|
|
925
|
+
params.set("project", dsn.projectId);
|
|
926
|
+
const path = `performance/trace/${encodedTraceId}/?${params.toString()}`;
|
|
927
|
+
if (isSentrySaasDsnHost(dsn.host)) {
|
|
928
|
+
return `https://${orgSlug}.sentry.io/${path}`;
|
|
929
|
+
}
|
|
930
|
+
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path}`;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export {
|
|
934
|
+
GET,
|
|
935
|
+
loadProjection,
|
|
936
|
+
loadConnectedMcpProviders,
|
|
937
|
+
recordMcpProviderConnected,
|
|
938
|
+
recordAuthorizationRequested,
|
|
939
|
+
recordAuthorizationCompleted,
|
|
940
|
+
commitMessages,
|
|
941
|
+
getAgentTurnSessionRecord,
|
|
942
|
+
upsertAgentTurnSessionRecord,
|
|
943
|
+
recordAgentTurnSessionSummary,
|
|
944
|
+
listAgentTurnSessionSummaries,
|
|
945
|
+
listAgentTurnSessionSummariesForConversation,
|
|
946
|
+
abandonAgentTurnSessionRecord,
|
|
947
|
+
failAgentTurnSessionRecord,
|
|
948
|
+
buildSentryConversationUrl,
|
|
949
|
+
buildSentryTraceUrl
|
|
950
|
+
};
|