@sentry/junior 0.148.0 → 0.150.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.
Files changed (84) hide show
  1. package/dist/{agent-hooks-VAUYT6PD.js → agent-hooks-SEOGOGKX.js} +10 -10
  2. package/dist/api/conversations/aggregate.d.ts +2 -0
  3. package/dist/api/conversations/create.d.ts +1 -1
  4. package/dist/api/conversations/pending-messages.d.ts +15 -0
  5. package/dist/api/people/shared.d.ts +2 -0
  6. package/dist/api/schema/conversation.d.ts +78 -4
  7. package/dist/api/schema.d.ts +2 -2
  8. package/dist/api/schema.js +7 -1
  9. package/dist/api.d.ts +1 -1
  10. package/dist/api.js +186 -35
  11. package/dist/app.js +152 -186
  12. package/dist/{catalog-runtime-WADOZVTB.js → catalog-runtime-HA7RIWYT.js} +5 -5
  13. package/dist/chat/agent/index.d.ts +5 -4
  14. package/dist/chat/agent/prompt.d.ts +8 -5
  15. package/dist/chat/agent/resume.d.ts +2 -2
  16. package/dist/chat/agent/tools.d.ts +4 -7
  17. package/dist/chat/agent/{request.d.ts → types.d.ts} +138 -129
  18. package/dist/chat/agent-dispatch/types.d.ts +2 -2
  19. package/dist/chat/agent-invocations/spawn.d.ts +2 -2
  20. package/dist/chat/api-turns/work.d.ts +6 -1
  21. package/dist/chat/app/services.d.ts +1 -1
  22. package/dist/chat/plugins/viewer.d.ts +4 -0
  23. package/dist/chat/runtime/agent-runner.d.ts +7 -5
  24. package/dist/chat/runtime/reply-executor.d.ts +0 -2
  25. package/dist/chat/runtime/slack-resume.d.ts +3 -3
  26. package/dist/chat/services/conversation-memory.d.ts +2 -2
  27. package/dist/chat/services/conversation-title.d.ts +20 -0
  28. package/dist/chat/slack/assistant-thread/title.d.ts +10 -27
  29. package/dist/chat/state/conversation.d.ts +1 -0
  30. package/dist/chat/task-execution/paused-turn.d.ts +2 -2
  31. package/dist/chat/tools/types.d.ts +1 -1
  32. package/dist/{chunk-QOWHHM3F.js → chunk-3GL327SC.js} +1 -1
  33. package/dist/{chunk-45LOR4PU.js → chunk-3JVUWLR7.js} +45 -1
  34. package/dist/{chunk-4GXJUYJV.js → chunk-3ZMKDHCT.js} +2 -2
  35. package/dist/{chunk-DUY5CDAM.js → chunk-4KAYAIBV.js} +1 -1
  36. package/dist/{chunk-ULAU5ZGU.js → chunk-4VH7KLGC.js} +106 -113
  37. package/dist/{chunk-V2FXMOEA.js → chunk-64K7GKRW.js} +2 -2
  38. package/dist/{chunk-2B3PT3V2.js → chunk-6PFDWAK2.js} +1 -1
  39. package/dist/{chunk-LNVEIJ2L.js → chunk-7D4GNUV2.js} +5 -5
  40. package/dist/{chunk-ZPL2RR7F.js → chunk-7GOP6VFJ.js} +40 -36
  41. package/dist/{chunk-HWQGBIH7.js → chunk-7NLW2NF5.js} +1 -1
  42. package/dist/{chunk-55F4QU2V.js → chunk-7QB2CKCW.js} +1 -1
  43. package/dist/{chunk-WCN4YSH7.js → chunk-DWETNV66.js} +6 -6
  44. package/dist/{chunk-D3WPMGEX.js → chunk-F5NMSEBL.js} +1 -1
  45. package/dist/{chunk-IL4EDBMV.js → chunk-FEFEIA3Z.js} +5 -5
  46. package/dist/{chunk-WOONT5ZP.js → chunk-H2RLIJ7G.js} +1 -1
  47. package/dist/{chunk-VRHTNBA6.js → chunk-ILYL6YFD.js} +3 -3
  48. package/dist/{chunk-VCYSOXON.js → chunk-IPGNWXA3.js} +545 -7
  49. package/dist/{chunk-LAZGHEYU.js → chunk-IZRSLANA.js} +1 -1
  50. package/dist/{chunk-EAI67NTM.js → chunk-JQXJ2E53.js} +1 -1
  51. package/dist/{chunk-HLPLT3NY.js → chunk-M5OZSVJI.js} +8 -4
  52. package/dist/{chunk-XJ42NBP5.js → chunk-MCNP7A7T.js} +8 -8
  53. package/dist/{chunk-OJCP3C3J.js → chunk-NCGVQXX5.js} +22 -26
  54. package/dist/{chunk-4AQZYNHM.js → chunk-NNOCRVQO.js} +2 -2
  55. package/dist/{chunk-GXYDVZKM.js → chunk-P43QM7JY.js} +5 -5
  56. package/dist/{chunk-EINWQFXI.js → chunk-QPIPYVGE.js} +5 -5
  57. package/dist/{chunk-GDFF3BQG.js → chunk-RF6J3DIO.js} +34 -36
  58. package/dist/{chunk-D6YX4NNN.js → chunk-SH6ZKJFZ.js} +4 -4
  59. package/dist/{chunk-LE5H7LMT.js → chunk-SHDREBGC.js} +1 -1
  60. package/dist/{chunk-VMNRXKAH.js → chunk-TZLYAO2J.js} +191 -134
  61. package/dist/{chunk-RRJV5UR5.js → chunk-UIMBNY6O.js} +11 -337
  62. package/dist/{chunk-ZCHIC2GM.js → chunk-UOFQEVR5.js} +15 -6
  63. package/dist/{chunk-7OEIXFC3.js → chunk-Y2VZ54CQ.js} +12 -12
  64. package/dist/{chunk-VY6W3T43.js → chunk-ZBOGYZ53.js} +4 -4
  65. package/dist/{chunk-POSZWRGL.js → chunk-ZUQT35YC.js} +1 -1
  66. package/dist/cli/chat.js +31 -32
  67. package/dist/cli/check.js +6 -6
  68. package/dist/cli/plugins.js +13 -13
  69. package/dist/cli/snapshot-create.js +9 -9
  70. package/dist/cli/upgrade.js +7 -7
  71. package/dist/{db-XUKZRFFK.js → db-HUW75QG7.js} +7 -7
  72. package/dist/instrumentation.js +4 -4
  73. package/dist/nitro.js +2 -2
  74. package/dist/{oauth-callback-server-LPZMMGFI.js → oauth-callback-server-DP7NIZPQ.js} +24 -25
  75. package/dist/{runner-DGKUTMPX.js → runner-AINV7K3M.js} +47 -45
  76. package/dist/{skills-P7YUFSXM.js → skills-PKS37E6B.js} +6 -6
  77. package/dist/spawn-NJCDEPPL.js +38 -0
  78. package/dist/{validation-S7U5M5NR.js → validation-PDC3MVMM.js} +6 -6
  79. package/dist/version.js +1 -1
  80. package/dist/{work-GAA7TVAD.js → work-KJLJRPXS.js} +17 -17
  81. package/dist/{worker-JXPJH3QB.js → worker-TYQ7YVIR.js} +11 -11
  82. package/package.json +5 -5
  83. package/dist/chunk-4EA2SFK2.js +0 -137
  84. package/dist/spawn-DSK3ZEVD.js +0 -38
@@ -13,7 +13,7 @@ import {
13
13
  instructionProvenanceFor,
14
14
  sanitizePostgresJson,
15
15
  withConversationEventLock
16
- } from "./chunk-VRHTNBA6.js";
16
+ } from "./chunk-ILYL6YFD.js";
17
17
  import {
18
18
  SANDBOX_WORKSPACE_ROOT,
19
19
  sleep
@@ -23,18 +23,26 @@ import {
23
23
  fenceLock,
24
24
  getStateAdapter,
25
25
  withLock
26
- } from "./chunk-2B3PT3V2.js";
26
+ } from "./chunk-6PFDWAK2.js";
27
27
  import {
28
28
  botConfig,
29
+ completeText,
30
+ escapeXml,
31
+ resolveGatewayModel,
29
32
  unwrapCurrentInstruction
30
- } from "./chunk-4GXJUYJV.js";
33
+ } from "./chunk-3ZMKDHCT.js";
31
34
  import {
32
35
  toStoredSlackActor
33
36
  } from "./chunk-HZF5P5UN.js";
37
+ import {
38
+ parseSlackMessageTs
39
+ } from "./chunk-F3B2YJ76.js";
34
40
  import {
35
41
  getActiveTraceId,
36
- logException
37
- } from "./chunk-EAI67NTM.js";
42
+ logException,
43
+ logWarn,
44
+ setSpanAttributes
45
+ } from "./chunk-JQXJ2E53.js";
38
46
 
39
47
  // src/chat/turn-context-tag.ts
40
48
  var TURN_CONTEXT_TAG = "runtime-turn-context";
@@ -173,10 +181,518 @@ function stripRuntimeTurnContext(messages) {
173
181
  });
174
182
  }
175
183
 
184
+ // src/chat/services/context-budget.ts
185
+ var COMPACTION_TRIGGER_RATIO = 0.9;
186
+ var CONTEXT_INPUT_LIMIT_RATIO = 0.95;
187
+ var COMPACTION_TARGET_RATIO = 0.8;
188
+ var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
189
+ function positiveInteger(value, fallback) {
190
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
191
+ }
192
+ function estimateTextTokens(text) {
193
+ return Math.ceil(text.length / 4);
194
+ }
195
+ function calculateContextCompactionTriggerTokens(model) {
196
+ const contextWindow = positiveInteger(
197
+ model.contextWindow,
198
+ FALLBACK_CONTEXT_WINDOW_TOKENS
199
+ );
200
+ return Math.max(1, Math.floor(contextWindow * COMPACTION_TRIGGER_RATIO));
201
+ }
202
+ function calculateContextInputLimitTokens(model) {
203
+ const contextWindow = positiveInteger(
204
+ model.contextWindow,
205
+ FALLBACK_CONTEXT_WINDOW_TOKENS
206
+ );
207
+ return Math.max(1, Math.floor(contextWindow * CONTEXT_INPUT_LIMIT_RATIO));
208
+ }
209
+ function calculateContextCompactionTargetTokens(triggerTokens) {
210
+ return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
211
+ }
212
+ function getModelContextBudget(modelId) {
213
+ const model = resolveGatewayModel(modelId);
214
+ const advertisedContextWindow = positiveInteger(
215
+ model.contextWindow,
216
+ FALLBACK_CONTEXT_WINDOW_TOKENS
217
+ );
218
+ return {
219
+ contextWindow: Math.min(
220
+ botConfig.contextWindowTokens,
221
+ advertisedContextWindow
222
+ )
223
+ };
224
+ }
225
+ function getAgentContextCompactionTriggerTokens(modelId) {
226
+ return calculateContextCompactionTriggerTokens(
227
+ getModelContextBudget(modelId)
228
+ );
229
+ }
230
+ function getAgentContextInputLimitTokens(modelId) {
231
+ return calculateContextInputLimitTokens(getModelContextBudget(modelId));
232
+ }
233
+ function getConversationContextCompactionTriggerTokens() {
234
+ return calculateContextCompactionTriggerTokens(
235
+ getModelContextBudget(botConfig.fastModelId)
236
+ );
237
+ }
238
+
239
+ // src/chat/services/conversation-memory.ts
240
+ var CONTEXT_MIN_LIVE_MESSAGES = 12;
241
+ var CONTEXT_COMPACTION_BATCH_SIZE = 24;
242
+ var CONTEXT_MAX_COMPACTIONS = 16;
243
+ var CONTEXT_MAX_MESSAGE_CHARS = 3200;
244
+ var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
245
+ function generateConversationId(prefix) {
246
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
247
+ }
248
+ function normalizeConversationText(text) {
249
+ return text.replace(/\r\n?/g, "\n").trim();
250
+ }
251
+ function buildImageContextSuffix(message, conversation) {
252
+ const byFileId = conversation?.vision.byFileId;
253
+ const imageFileIds = message.meta?.imageFileIds ?? [];
254
+ if (!byFileId || imageFileIds.length === 0) {
255
+ return "";
256
+ }
257
+ const summaries = imageFileIds.map((fileId) => byFileId[fileId]?.summary?.trim()).filter((summary) => Boolean(summary));
258
+ if (summaries.length === 0) {
259
+ return "";
260
+ }
261
+ return ` [image context: ${summaries.join(" | ")}]`;
262
+ }
263
+ function renderConversationMessageLine(message, conversation) {
264
+ const displayName = conversationAuthorDisplayName(message);
265
+ const text = message.text.replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS);
266
+ const markers = [];
267
+ if (message.meta?.replied === false) {
268
+ markers.push(
269
+ `assistant skipped: ${message.meta?.skippedReason ?? "no-reply route"}`
270
+ );
271
+ }
272
+ if (message.meta?.explicitMention) {
273
+ markers.push("explicit mention");
274
+ }
275
+ const markerSuffix = markers.length > 0 ? ` (${markers.join("; ")})` : "";
276
+ const imageContext = buildImageContextSuffix(message, conversation);
277
+ return `[${message.role}] ${displayName}: ${text}${imageContext}${markerSuffix}`;
278
+ }
279
+ function conversationAuthorDisplayName(message) {
280
+ const author = message.author;
281
+ const fullName = authorDisplayField(author?.fullName, author?.userId);
282
+ const userName = authorDisplayField(author?.userName, author?.userId);
283
+ return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
284
+ }
285
+ function authorDisplayField(value, userId) {
286
+ if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
287
+ return void 0;
288
+ }
289
+ return value;
290
+ }
291
+ function estimateConversationContextTokens(conversation) {
292
+ return estimateTextTokens(buildConversationContext(conversation) ?? "");
293
+ }
294
+ function upsertConversationMessage(conversation, message) {
295
+ const existingIndex = conversation.messages.findIndex(
296
+ (entry) => entry.id === message.id
297
+ );
298
+ if (existingIndex >= 0) {
299
+ conversation.messages[existingIndex] = {
300
+ ...conversation.messages[existingIndex],
301
+ ...message,
302
+ meta: {
303
+ ...conversation.messages[existingIndex]?.meta,
304
+ ...message.meta
305
+ }
306
+ };
307
+ return message.id;
308
+ }
309
+ conversation.messages.push(message);
310
+ return message.id;
311
+ }
312
+ function recordDeliveredAssistantMessage(args) {
313
+ const prefix = `${args.sessionId}:assistant:`;
314
+ const ordinal = args.conversation.messages.filter(
315
+ (message) => message.id.startsWith(prefix)
316
+ ).length + 1;
317
+ const messageId = `${prefix}${ordinal}`;
318
+ markConversationMessage(args.conversation, args.userMessageId, {
319
+ replied: true,
320
+ skippedReason: void 0
321
+ });
322
+ upsertConversationMessage(args.conversation, {
323
+ id: messageId,
324
+ role: "assistant",
325
+ text: normalizeConversationText(args.text),
326
+ createdAtMs: Date.now(),
327
+ author: {
328
+ userName: botConfig.userName,
329
+ isBot: true
330
+ },
331
+ meta: {
332
+ replied: true,
333
+ ...args.source ? { source: args.source } : {}
334
+ }
335
+ });
336
+ return messageId;
337
+ }
338
+ function turnHasReply(conversation, turnId) {
339
+ const assistantPrefix = `${turnId}:assistant:`;
340
+ return conversation.messages.some(
341
+ (message) => message.role === "assistant" && message.id.startsWith(assistantPrefix)
342
+ );
343
+ }
344
+ function markConversationMessage(conversation, messageId, patch) {
345
+ if (!messageId) return;
346
+ const messageIndex = conversation.messages.findIndex(
347
+ (entry) => entry.id === messageId
348
+ );
349
+ if (messageIndex < 0) return;
350
+ const current = conversation.messages[messageIndex];
351
+ conversation.messages[messageIndex] = {
352
+ ...current,
353
+ meta: {
354
+ ...current.meta ?? {},
355
+ ...patch
356
+ }
357
+ };
358
+ }
359
+ function buildConversationContext(conversation, options = {}) {
360
+ const messages = conversation.messages.filter(
361
+ (entry) => entry.id !== options.excludeMessageId
362
+ );
363
+ if (messages.length === 0 && conversation.compactions.length === 0) {
364
+ return void 0;
365
+ }
366
+ const lines = [];
367
+ if (conversation.compactions.length > 0) {
368
+ lines.push("<thread-compactions>");
369
+ for (const [index, compaction] of conversation.compactions.entries()) {
370
+ lines.push(
371
+ ` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageCount}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
372
+ compaction.summary,
373
+ " </compaction>"
374
+ );
375
+ }
376
+ lines.push("</thread-compactions>");
377
+ }
378
+ if (messages.length > 0) {
379
+ if (lines.length > 0) {
380
+ lines.push("");
381
+ }
382
+ lines.push("<thread-transcript>");
383
+ for (const [index, message] of messages.entries()) {
384
+ const author = escapeXml(conversationAuthorDisplayName(message));
385
+ const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
386
+ const ts = new Date(message.createdAtMs).toISOString();
387
+ const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
388
+ lines.push(
389
+ ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
390
+ renderConversationMessageLine(message, conversation),
391
+ " </message>"
392
+ );
393
+ }
394
+ lines.push("</thread-transcript>");
395
+ }
396
+ return lines.join("\n");
397
+ }
398
+ function pruneCompactions(compactions) {
399
+ if (compactions.length <= CONTEXT_MAX_COMPACTIONS) {
400
+ return compactions;
401
+ }
402
+ const overflowCount = compactions.length - CONTEXT_MAX_COMPACTIONS + 1;
403
+ const merged = compactions.slice(0, overflowCount);
404
+ const mergedSummary = merged.map((entry) => entry.summary).join("\n").slice(0, 3500);
405
+ const coveredMessageCount = merged.reduce(
406
+ (count, entry) => count + entry.coveredMessageCount,
407
+ 0
408
+ );
409
+ const compacted = {
410
+ id: generateConversationId("compaction"),
411
+ createdAtMs: Date.now(),
412
+ summary: mergedSummary,
413
+ coveredMessageCount
414
+ };
415
+ return [compacted, ...compactions.slice(overflowCount)];
416
+ }
417
+ async function summarizeConversationChunk(messages, conversation, context, deps) {
418
+ const transcript = messages.map((message) => renderConversationMessageLine(message, conversation)).join("\n");
419
+ try {
420
+ const result = await deps.completeText({
421
+ modelId: botConfig.fastModelId,
422
+ temperature: 0,
423
+ promptName: "junior.thread_compaction",
424
+ messages: [
425
+ {
426
+ role: "user",
427
+ content: [
428
+ "Summarize the following older Slack thread transcript segment for future assistant turns.",
429
+ "Keep the summary factual and concise. Do not invent details.",
430
+ "",
431
+ "Output exactly three XML sections in this order:",
432
+ "<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>",
433
+ "<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>",
434
+ "<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>",
435
+ "",
436
+ "Do not output any text outside the three sections.",
437
+ "",
438
+ transcript
439
+ ].join("\n"),
440
+ timestamp: Date.now()
441
+ }
442
+ ],
443
+ metadata: {
444
+ modelId: botConfig.fastModelId,
445
+ threadId: context.threadId ?? "",
446
+ channelId: context.channelId ?? "",
447
+ actorId: context.actorId ?? "",
448
+ runId: context.runId ?? ""
449
+ }
450
+ });
451
+ const summary = result.text.trim();
452
+ if (summary.length > 0) {
453
+ return summary.slice(0, 3500);
454
+ }
455
+ } catch (error) {
456
+ logWarn("conversation.compaction.summary.failed", {
457
+ "exception.message": error instanceof Error ? error.message : String(error),
458
+ "app.compaction_messages_covered": messages.length
459
+ });
460
+ }
461
+ return transcript.slice(0, 2800);
462
+ }
463
+ function getThreadTitleSourceMessage(conversation) {
464
+ let firstMessage;
465
+ for (const message of conversation.messages) {
466
+ if (!isHumanConversationMessage(message)) {
467
+ continue;
468
+ }
469
+ if (!firstMessage) {
470
+ firstMessage = message;
471
+ continue;
472
+ }
473
+ if (message.createdAtMs < firstMessage.createdAtMs) {
474
+ firstMessage = message;
475
+ continue;
476
+ }
477
+ if (message.createdAtMs === firstMessage.createdAtMs && message.id < firstMessage.id) {
478
+ firstMessage = message;
479
+ }
480
+ }
481
+ return firstMessage;
482
+ }
483
+ async function compactConversationIfNeededWithDeps(conversation, context, deps) {
484
+ let estimatedTokens = estimateConversationContextTokens(conversation);
485
+ setSpanAttributes({
486
+ "app.context_tokens_estimated": estimatedTokens
487
+ });
488
+ const triggerTokens = getConversationContextCompactionTriggerTokens();
489
+ const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
490
+ while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
491
+ const compactCount = Math.min(
492
+ CONTEXT_COMPACTION_BATCH_SIZE,
493
+ conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
494
+ );
495
+ if (compactCount <= 0) {
496
+ break;
497
+ }
498
+ const compactedChunk = conversation.messages.slice(0, compactCount);
499
+ const summary = await summarizeConversationChunk(
500
+ compactedChunk,
501
+ conversation,
502
+ context,
503
+ deps
504
+ );
505
+ conversation.compactions.push({
506
+ id: generateConversationId("compaction"),
507
+ createdAtMs: Date.now(),
508
+ summary,
509
+ coveredMessageCount: compactedChunk.length
510
+ });
511
+ conversation.compactions = pruneCompactions(conversation.compactions);
512
+ conversation.messages = conversation.messages.slice(compactCount);
513
+ estimatedTokens = estimateConversationContextTokens(conversation);
514
+ setSpanAttributes({
515
+ "app.compaction_messages_covered": compactCount,
516
+ "app.compaction.trigger_tokens": triggerTokens,
517
+ "app.compaction.target_tokens": targetTokens,
518
+ "app.context_tokens_estimated": estimatedTokens
519
+ });
520
+ if (estimatedTokens <= targetTokens) {
521
+ break;
522
+ }
523
+ }
524
+ }
525
+ function createConversationMemoryService(deps) {
526
+ return {
527
+ compactConversationIfNeeded: async (conversation, context) => await compactConversationIfNeededWithDeps(conversation, context, deps)
528
+ };
529
+ }
530
+ function isHumanConversationMessage(message) {
531
+ return message.role === "user" && message.author?.isBot !== true;
532
+ }
533
+ function getConversationMessageSlackTs(message) {
534
+ return parseSlackMessageTs(message.meta?.slackTs);
535
+ }
536
+
176
537
  // src/chat/conversations/messages.ts
177
538
  import { createHash } from "crypto";
178
539
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
179
540
 
541
+ // src/chat/services/short-title.ts
542
+ var SHORT_TITLE_MAX_LENGTH = 60;
543
+ var TITLE_PROMPTS = {
544
+ conversation: [
545
+ "Generate a concise 5-8 word conversation title from the first user message below.",
546
+ "Capture the user's main request.",
547
+ "Reply with ONLY the title, with no quotes or trailing punctuation."
548
+ ],
549
+ task: [
550
+ "Generate a concise 5-8 word task title from the task instruction below.",
551
+ "Capture the task's main purpose.",
552
+ "Reply with ONLY the title, with no quotes or trailing punctuation."
553
+ ]
554
+ };
555
+ function normalizeShortTitle(value) {
556
+ return value.replace(/\s+/g, " ").trim().slice(0, SHORT_TITLE_MAX_LENGTH);
557
+ }
558
+ function fallbackShortTitle(sourceText, fallback = "Untitled") {
559
+ const firstLine = sourceText.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? "";
560
+ return normalizeShortTitle(firstLine) || fallback;
561
+ }
562
+ async function generateShortTitle(args) {
563
+ const sourceText = args.sourceText.trim();
564
+ if (!sourceText) return void 0;
565
+ try {
566
+ const result = await args.completeText({
567
+ modelId: botConfig.fastModelId,
568
+ temperature: 0,
569
+ promptName: `junior.${args.kind}_title`,
570
+ messages: [
571
+ {
572
+ role: "user",
573
+ content: [
574
+ ...TITLE_PROMPTS[args.kind],
575
+ "",
576
+ args.kind === "conversation" ? `First user message: ${sourceText.slice(0, 500)}` : `Task instruction: ${sourceText.slice(0, 500)}`
577
+ ].join("\n"),
578
+ timestamp: Date.now()
579
+ }
580
+ ],
581
+ metadata: {
582
+ modelId: botConfig.fastModelId
583
+ }
584
+ });
585
+ const title = normalizeShortTitle(result.text);
586
+ return title || void 0;
587
+ } catch (error) {
588
+ logWarn(`${args.kind}.title.generation.failed`, {
589
+ "exception.message": error instanceof Error ? error.message : String(error)
590
+ });
591
+ return void 0;
592
+ }
593
+ }
594
+ async function resolveTaskTitle(args) {
595
+ const explicit = normalizeShortTitle(args.title ?? "");
596
+ if (explicit) return explicit;
597
+ return generateShortTitle({
598
+ completeText: args.completeText,
599
+ kind: "task",
600
+ sourceText: args.instruction
601
+ });
602
+ }
603
+
604
+ // src/chat/services/conversation-title.ts
605
+ var inFlightTitles = /* @__PURE__ */ new Map();
606
+ async function ensureConversationTitle(args) {
607
+ const pending = inFlightTitles.get(args.conversationId);
608
+ if (pending) {
609
+ return pending;
610
+ }
611
+ const work = ensureConversationTitleOnce(args).finally(() => {
612
+ inFlightTitles.delete(args.conversationId);
613
+ });
614
+ inFlightTitles.set(args.conversationId, work);
615
+ return work;
616
+ }
617
+ async function ensureConversationTitleOnce(args) {
618
+ if (!args.conversation.messages.some(isHumanConversationMessage)) {
619
+ return void 0;
620
+ }
621
+ const store = args.conversationStore ?? getConversationStore();
622
+ try {
623
+ const stored = await store.get({ conversationId: args.conversationId });
624
+ if (stored?.title?.trim()) {
625
+ return void 0;
626
+ }
627
+ if (stored?.lineage) {
628
+ return void 0;
629
+ }
630
+ const titleSourceMessage = getThreadTitleSourceMessage(args.conversation);
631
+ const sourceText = titleSourceMessage?.text.trim() ?? "";
632
+ if (!titleSourceMessage || !sourceText) {
633
+ return void 0;
634
+ }
635
+ let title;
636
+ try {
637
+ const generated = await generateShortTitle({
638
+ completeText,
639
+ kind: "conversation",
640
+ sourceText
641
+ });
642
+ title = generated ?? fallbackShortTitle(sourceText, "Conversation");
643
+ } catch (error) {
644
+ logWarn("conversation.title.generation.failed", {
645
+ "exception.message": error instanceof Error ? error.message : String(error)
646
+ });
647
+ title = fallbackShortTitle(sourceText, "Conversation");
648
+ }
649
+ const normalized = title.trim();
650
+ if (!normalized) {
651
+ return void 0;
652
+ }
653
+ const activityAtMs = titleSourceMessage.createdAtMs;
654
+ const nowMs = args.nowMs ?? Date.now();
655
+ try {
656
+ await store.recordActivity({
657
+ activityAtMs,
658
+ conversationId: args.conversationId,
659
+ nowMs,
660
+ title: normalized
661
+ });
662
+ } catch (error) {
663
+ logException(error, "conversation.title.persist.failed");
664
+ return void 0;
665
+ }
666
+ return normalized;
667
+ } catch (error) {
668
+ logException(error, "conversation.title.ensure.failed");
669
+ return void 0;
670
+ }
671
+ }
672
+ function scheduleConversationTitle(args) {
673
+ if (!args.conversationId) {
674
+ return;
675
+ }
676
+ void ensureConversationTitle({
677
+ conversation: args.conversation,
678
+ conversationId: args.conversationId
679
+ }).catch((error) => {
680
+ logException(error, "conversation.title.task.failed");
681
+ });
682
+ }
683
+ async function resolveConversationTitle(args) {
684
+ const pending = inFlightTitles.get(args.conversationId);
685
+ if (pending) {
686
+ const generated = await pending;
687
+ if (generated?.trim()) {
688
+ return generated.trim();
689
+ }
690
+ }
691
+ const store = args.conversationStore ?? getConversationStore();
692
+ const stored = await store.get({ conversationId: args.conversationId });
693
+ return stored?.title?.trim() || void 0;
694
+ }
695
+
180
696
  // src/chat/conversations/message-summaries.ts
181
697
  import { isDeepStrictEqual } from "util";
182
698
  function projectConversationMessageSummaries(events) {
@@ -320,6 +836,10 @@ async function persistConversationMessages(args) {
320
836
  ...args,
321
837
  conversationId: args.conversationId
322
838
  });
839
+ scheduleConversationTitle({
840
+ conversation: args.conversation,
841
+ conversationId: args.conversationId
842
+ });
323
843
  }
324
844
  async function appendConversationMessages(eventStore, args) {
325
845
  if (args.conversation.messages.length === 0) return;
@@ -1276,7 +1796,7 @@ var RIPGREP_EXCLUDED_GLOBS = [
1276
1796
  ];
1277
1797
  var DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
1278
1798
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
1279
- function positiveInteger(value) {
1799
+ function positiveInteger2(value) {
1280
1800
  if (typeof value !== "number" || !Number.isFinite(value)) {
1281
1801
  return void 0;
1282
1802
  }
@@ -2615,6 +3135,24 @@ export {
2615
3135
  stripRuntimeTurnContext,
2616
3136
  historyItemFromPiMessage,
2617
3137
  piMessageFromHistoryItem,
3138
+ estimateTextTokens,
3139
+ getAgentContextCompactionTriggerTokens,
3140
+ getAgentContextInputLimitTokens,
3141
+ normalizeConversationText,
3142
+ estimateConversationContextTokens,
3143
+ upsertConversationMessage,
3144
+ recordDeliveredAssistantMessage,
3145
+ turnHasReply,
3146
+ markConversationMessage,
3147
+ buildConversationContext,
3148
+ createConversationMemoryService,
3149
+ isHumanConversationMessage,
3150
+ getConversationMessageSlackTs,
3151
+ SHORT_TITLE_MAX_LENGTH,
3152
+ fallbackShortTitle,
3153
+ generateShortTitle,
3154
+ resolveTaskTitle,
3155
+ resolveConversationTitle,
2618
3156
  persistConversationMessageSummaries,
2619
3157
  hydrateConversationMessages,
2620
3158
  persistConversationMessages,
@@ -2638,7 +3176,7 @@ export {
2638
3176
  MAX_TEXT_CHARS,
2639
3177
  RIPGREP_EXCLUDED_GLOBS,
2640
3178
  DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
2641
- positiveInteger,
3179
+ positiveInteger2 as positiveInteger,
2642
3180
  normalizeToLf,
2643
3181
  truncateText,
2644
3182
  isMissingPathError,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  JUNIOR_VERSION
3
- } from "./chunk-55F4QU2V.js";
3
+ } from "./chunk-7QB2CKCW.js";
4
4
 
5
5
  // src/deployment.ts
6
6
  var JUNIOR_HEARTBEAT_ROUTE = "/api/internal/heartbeat";
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-6GWA276C.js";
10
10
  import {
11
11
  getDeploymentTelemetryAttributes
12
- } from "./chunk-LAZGHEYU.js";
12
+ } from "./chunk-IZRSLANA.js";
13
13
 
14
14
  // src/chat/logging.ts
15
15
  import path from "path";
@@ -8,14 +8,14 @@ import {
8
8
  getConversationStore,
9
9
  parseDestination,
10
10
  sameDestination
11
- } from "./chunk-VRHTNBA6.js";
11
+ } from "./chunk-ILYL6YFD.js";
12
12
  import {
13
13
  getDefaultRedisStateAdapterFor,
14
14
  getStateAdapter
15
- } from "./chunk-2B3PT3V2.js";
15
+ } from "./chunk-6PFDWAK2.js";
16
16
  import {
17
17
  getChatConfig
18
- } from "./chunk-4GXJUYJV.js";
18
+ } from "./chunk-3ZMKDHCT.js";
19
19
  import {
20
20
  parseStoredSlackActor
21
21
  } from "./chunk-HZF5P5UN.js";
@@ -23,7 +23,7 @@ import {
23
23
  isRecord,
24
24
  toOptionalNumber,
25
25
  toOptionalString
26
- } from "./chunk-EAI67NTM.js";
26
+ } from "./chunk-JQXJ2E53.js";
27
27
 
28
28
  // src/chat/task-execution/state.ts
29
29
  import { randomUUID } from "crypto";
@@ -1300,6 +1300,9 @@ async function recordExecutionMetadata(args) {
1300
1300
  updatedAtMs: conversation.updatedAtMs
1301
1301
  });
1302
1302
  }
1303
+ async function getConversation2(args) {
1304
+ return await getConversation(args);
1305
+ }
1303
1306
  async function getConversationWorkState2(args) {
1304
1307
  return await getConversationWorkState(args);
1305
1308
  }
@@ -1500,6 +1503,7 @@ export {
1500
1503
  CONVERSATION_WORK_STALE_ENQUEUE_MS,
1501
1504
  CONVERSATION_WORK_MAX_RETRIES,
1502
1505
  isFinalAttempt,
1506
+ getConversation2 as getConversation,
1503
1507
  getConversationWorkState2 as getConversationWorkState,
1504
1508
  recordConversationRetry2 as recordConversationRetry,
1505
1509
  countPendingConversationMessages,