pi-sessions 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,4 @@
1
- import {
2
- type Api,
3
- completeSimple,
4
- type Model,
5
- type TextContent,
6
- type UserMessage,
7
- } from "@earendil-works/pi-ai";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
8
2
  import type {
9
3
  ExtensionAPI,
10
4
  ExtensionCommandContext,
@@ -13,27 +7,19 @@ import type {
13
7
  } from "@earendil-works/pi-coding-agent";
14
8
  import { SessionManager } from "@earendil-works/pi-coding-agent";
15
9
  import type { RetitleMode, RetitleScope } from "./command.js";
16
- import { type AutoTitleContext, buildAutoTitleContext } from "./context.js";
17
- import type {
18
- AutoTitleFailure,
19
- AutoTitleTriggerPlan,
20
- SessionAutoTitleController,
21
- } from "./controller.js";
10
+ import { buildAutoTitleContext } from "./context.js";
11
+ import type { AutoTitleTriggerPlan, SessionAutoTitleController } from "./controller.js";
22
12
  import {
23
- AUTO_TITLE_SYSTEM_PROMPT,
24
- buildAutoTitlePrompt,
25
- normalizeGeneratedAutoTitle,
26
- } from "./prompt.js";
13
+ type AutoTitleGenerationResult,
14
+ createAutoTitleFailure,
15
+ generateAutoTitle,
16
+ } from "./generate.js";
27
17
  import {
28
18
  AUTO_TITLE_STATE_CUSTOM_TYPE,
29
19
  type AutoTitlePersistedState,
30
- type AutoTitleTrigger,
31
20
  createAutoTitleState,
32
21
  } from "./state.js";
33
22
 
34
- const AUTO_TITLE_REQUEST_TIMEOUT_MS = 15_000;
35
- const AUTO_TITLE_MAX_TOKENS = 64;
36
-
37
23
  export interface RetitleScopeScan {
38
24
  scope: Exclude<RetitleScope, "this">;
39
25
  sessions: SessionInfo[];
@@ -71,6 +57,7 @@ export async function runBulkRetitle(
71
57
  scan: RetitleScopeScan,
72
58
  mode: RetitleMode,
73
59
  getSessionEpoch: () => number,
60
+ systemPrompt: string,
74
61
  ): Promise<BulkRetitleResult> {
75
62
  const result: BulkRetitleResult = {
76
63
  attempted: 0,
@@ -97,6 +84,7 @@ export async function runBulkRetitle(
97
84
  ctx,
98
85
  model,
99
86
  isManual: true,
87
+ systemPrompt,
100
88
  getSessionEpoch,
101
89
  notifyOnSuccess: false,
102
90
  });
@@ -104,7 +92,7 @@ export async function runBulkRetitle(
104
92
  continue;
105
93
  }
106
94
 
107
- const outcome = await retitleStoredSession(ctx, model, session.path);
95
+ const outcome = await retitleStoredSession(ctx, model, session.path, systemPrompt);
108
96
  result[outcome] += 1;
109
97
  }
110
98
 
@@ -176,23 +164,17 @@ export interface RetitlePlanOptions {
176
164
  ctx: ExtensionContext;
177
165
  model: Model<Api> | undefined;
178
166
  isManual: boolean;
167
+ systemPrompt: string;
179
168
  existingPlan?: AutoTitleTriggerPlan;
180
169
  getSessionEpoch?: () => number;
181
170
  notifyOnSuccess?: boolean;
182
171
  }
183
172
 
184
- export type RetitlePlanResult =
185
- | {
186
- ok: true;
187
- title: string;
188
- }
189
- | {
190
- ok: false;
191
- failure: AutoTitleFailure;
192
- };
193
-
194
- export async function runRetitlePlan(options: RetitlePlanOptions): Promise<RetitlePlanResult> {
195
- const { pi, controller, ctx, model, isManual, existingPlan, getSessionEpoch } = options;
173
+ export async function runRetitlePlan(
174
+ options: RetitlePlanOptions,
175
+ ): Promise<AutoTitleGenerationResult> {
176
+ const { pi, controller, ctx, model, isManual, systemPrompt, existingPlan, getSessionEpoch } =
177
+ options;
196
178
  const notifyOnSuccess = options.notifyOnSuccess ?? isManual;
197
179
 
198
180
  const plan = existingPlan ?? controller.handleManualRetitle(ctx);
@@ -215,7 +197,21 @@ export async function runRetitlePlan(options: RetitlePlanOptions): Promise<Retit
215
197
  }
216
198
 
217
199
  const currentEpoch = getSessionEpoch?.();
218
- const generatedTitle = await generateAutoTitle(ctx, plan, model);
200
+ const titleContext = buildAutoTitleContext(
201
+ ctx.sessionManager.getEntries(),
202
+ ctx.sessionManager.getLeafId(),
203
+ {
204
+ cwd: ctx.cwd,
205
+ currentTitle: plan.currentTitle,
206
+ },
207
+ );
208
+ const generatedTitle = await generateAutoTitle(
209
+ ctx,
210
+ model,
211
+ titleContext,
212
+ plan.reason,
213
+ systemPrompt,
214
+ );
219
215
  if (!generatedTitle.ok) {
220
216
  return generatedTitle;
221
217
  }
@@ -260,6 +256,7 @@ async function retitleStoredSession(
260
256
  ctx: ExtensionContext,
261
257
  model: Model<Api>,
262
258
  sessionPath: string,
259
+ systemPrompt: string,
263
260
  ): Promise<"retitled" | "unchanged" | "failed"> {
264
261
  const sessionManager = SessionManager.open(sessionPath);
265
262
  const currentTitle = sessionManager.getSessionName();
@@ -276,7 +273,13 @@ async function retitleStoredSession(
276
273
  userTurnCount: titleContext.userTurnCount,
277
274
  currentTitle,
278
275
  };
279
- const generatedTitle = await generateAutoTitleFromContext(ctx, plan, model, titleContext);
276
+ const generatedTitle = await generateAutoTitle(
277
+ ctx,
278
+ model,
279
+ titleContext,
280
+ plan.reason,
281
+ systemPrompt,
282
+ );
280
283
  if (!generatedTitle.ok) {
281
284
  return "failed";
282
285
  }
@@ -300,342 +303,3 @@ async function retitleStoredSession(
300
303
  function hasSessionTitle(name: string | undefined): boolean {
301
304
  return Boolean(name?.trim());
302
305
  }
303
-
304
- async function generateAutoTitle(
305
- ctx: ExtensionContext,
306
- plan: AutoTitleTriggerPlan,
307
- model: Model<Api>,
308
- ): Promise<RetitlePlanResult> {
309
- const titleContext = buildAutoTitleContext(
310
- ctx.sessionManager.getEntries(),
311
- ctx.sessionManager.getLeafId(),
312
- {
313
- cwd: ctx.cwd,
314
- currentTitle: plan.currentTitle,
315
- },
316
- );
317
- return generateAutoTitleFromContext(ctx, plan, model, titleContext);
318
- }
319
-
320
- async function generateAutoTitleFromContext(
321
- ctx: ExtensionContext,
322
- plan: AutoTitleTriggerPlan,
323
- model: Model<Api>,
324
- titleContext: AutoTitleContext,
325
- ): Promise<RetitlePlanResult> {
326
- if (!titleContext.conversationText) {
327
- return {
328
- ok: false,
329
- failure: createAutoTitleFailure(
330
- plan.reason,
331
- model,
332
- "No conversation available for auto-title generation.",
333
- ),
334
- };
335
- }
336
-
337
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
338
- if (!auth.ok) {
339
- return {
340
- ok: false,
341
- failure: createAutoTitleFailure(
342
- plan.reason,
343
- model,
344
- "Failed to authenticate auto-title model.",
345
- ),
346
- };
347
- }
348
-
349
- const message: UserMessage = {
350
- role: "user",
351
- content: [{ type: "text", text: buildAutoTitlePrompt(titleContext, plan.reason) }],
352
- timestamp: Date.now(),
353
- };
354
-
355
- const abortController = new AbortController();
356
- const timeoutId = setTimeout(() => abortController.abort(), AUTO_TITLE_REQUEST_TIMEOUT_MS);
357
-
358
- try {
359
- const response = await completeSimple(
360
- model,
361
- {
362
- systemPrompt: AUTO_TITLE_SYSTEM_PROMPT,
363
- messages: [message],
364
- },
365
- {
366
- ...(auth.apiKey && { apiKey: auth.apiKey }),
367
- ...(auth.headers && { headers: auth.headers }),
368
- maxTokens: AUTO_TITLE_MAX_TOKENS,
369
- signal: abortController.signal,
370
- },
371
- );
372
-
373
- if (response.stopReason === "error" || response.stopReason === "aborted") {
374
- const fallbackMessage =
375
- response.stopReason === "aborted" ? "Request was aborted." : "Provider returned an error.";
376
- const failureDetails = extractFailureDetails(
377
- response.errorMessage || fallbackMessage,
378
- response,
379
- );
380
- return {
381
- ok: false,
382
- failure: createAutoTitleFailure(
383
- plan.reason,
384
- model,
385
- failureDetails.message,
386
- failureDetails.status,
387
- ),
388
- };
389
- }
390
-
391
- const responseText = response.content
392
- .filter((part): part is TextContent => part.type === "text")
393
- .map((part) => part.text)
394
- .join("\n");
395
- const normalizedTitle = normalizeGeneratedAutoTitle(responseText);
396
- if (!normalizedTitle) {
397
- return {
398
- ok: false,
399
- failure: createAutoTitleFailure(plan.reason, model, "Model returned an empty title."),
400
- };
401
- }
402
-
403
- return {
404
- ok: true,
405
- title: normalizedTitle,
406
- };
407
- } catch (error) {
408
- const failureDetails = extractFailureDetails(error);
409
- return {
410
- ok: false,
411
- failure: createAutoTitleFailure(
412
- plan.reason,
413
- model,
414
- failureDetails.message,
415
- failureDetails.status,
416
- ),
417
- };
418
- } finally {
419
- clearTimeout(timeoutId);
420
- }
421
- }
422
-
423
- function createAutoTitleFailure(
424
- trigger: AutoTitleTrigger,
425
- model: Model<Api> | undefined,
426
- message: string,
427
- status?: number,
428
- ): AutoTitleFailure {
429
- return {
430
- at: new Date().toISOString(),
431
- trigger,
432
- model: formatModelLabel(model),
433
- message,
434
- ...(status !== undefined ? { status } : {}),
435
- };
436
- }
437
-
438
- function formatModelLabel(model: Model<Api> | undefined): string {
439
- if (!model) {
440
- return "(no model resolved)";
441
- }
442
-
443
- return `${model.provider}/${model.id}`;
444
- }
445
-
446
- function extractFailureDetails(
447
- primary: unknown,
448
- secondary?: unknown,
449
- ): { message: string; status?: number } {
450
- const structured =
451
- parseStructuredProviderError(primary) ??
452
- parseStructuredProviderError(secondary) ??
453
- parseEmbeddedErrorFromObject(primary);
454
- if (structured) {
455
- return structured;
456
- }
457
-
458
- const message =
459
- extractStringMessage(primary) || extractStringMessage(secondary) || "Unknown provider error.";
460
- const status = extractStatus(primary) ?? extractStatus(secondary);
461
- return {
462
- message,
463
- ...(status !== undefined ? { status } : {}),
464
- };
465
- }
466
-
467
- function parseEmbeddedErrorFromObject(
468
- value: unknown,
469
- ): { message: string; status?: number } | undefined {
470
- if (!isObject(value) && !(value instanceof Error)) {
471
- return undefined;
472
- }
473
-
474
- const embedded = extractStringMessage(value);
475
- return embedded ? parseStructuredProviderError(embedded) : undefined;
476
- }
477
-
478
- function parseStructuredProviderError(
479
- value: unknown,
480
- ): { message: string; status?: number } | undefined {
481
- const json = parseJsonObjectCandidate(value);
482
- if (!json) {
483
- return undefined;
484
- }
485
-
486
- const topError = isObject(json.error) ? json.error : undefined;
487
- const status =
488
- readNumericStatus(json) ??
489
- readNumericStatus(topError) ??
490
- (typeof topError?.code === "number" ? topError.code : undefined);
491
- const rawMessage =
492
- readString(topError?.message) ?? readString(json.message) ?? readString(json.errorMessage);
493
- if (!rawMessage) {
494
- return undefined;
495
- }
496
-
497
- const labels = collectProviderErrorLabels(json, topError);
498
- return {
499
- message: formatStructuredErrorMessage(labels, rawMessage),
500
- ...(status !== undefined ? { status } : {}),
501
- };
502
- }
503
-
504
- function collectProviderErrorLabels(
505
- root: Record<string, unknown>,
506
- nestedError: Record<string, unknown> | undefined,
507
- ): string[] {
508
- const labels: string[] = [];
509
- const candidates = [
510
- readString(nestedError?.status),
511
- readString(nestedError?.type),
512
- readString(nestedError?.code),
513
- readString(root.status),
514
- readString(root.type),
515
- readString(root.code),
516
- ];
517
-
518
- for (const candidate of candidates) {
519
- if (!candidate) {
520
- continue;
521
- }
522
-
523
- const normalized = candidate.trim();
524
- if (!normalized || normalized === "error") {
525
- continue;
526
- }
527
-
528
- if (!labels.includes(normalized)) {
529
- labels.push(normalized);
530
- }
531
- }
532
-
533
- return labels;
534
- }
535
-
536
- function formatStructuredErrorMessage(labels: string[], rawMessage: string): string {
537
- const cleanedMessage = stripRedundantErrorPrefix(rawMessage);
538
- return labels.length > 0 ? `${labels.join(" · ")} · ${cleanedMessage}` : cleanedMessage;
539
- }
540
-
541
- function stripRedundantErrorPrefix(message: string): string {
542
- return message
543
- .replace(/^Unauthorized:\s*/i, "")
544
- .replace(/^Authentication (?:error|failed):\s*/i, "")
545
- .trim();
546
- }
547
-
548
- function parseJsonObjectCandidate(value: unknown): Record<string, unknown> | undefined {
549
- if (isObject(value)) {
550
- return value;
551
- }
552
-
553
- const text = extractStringMessage(value);
554
- if (!text) {
555
- return undefined;
556
- }
557
-
558
- const startIndex = text.indexOf("{");
559
- const endIndex = text.lastIndexOf("}");
560
- if (startIndex < 0 || endIndex <= startIndex) {
561
- return undefined;
562
- }
563
-
564
- try {
565
- const parsed = JSON.parse(text.slice(startIndex, endIndex + 1));
566
- return isObject(parsed) ? parsed : undefined;
567
- } catch {
568
- return undefined;
569
- }
570
- }
571
-
572
- function extractStringMessage(value: unknown): string | undefined {
573
- if (typeof value === "string" && value.trim()) {
574
- return value.trim();
575
- }
576
-
577
- if (value instanceof Error && value.message.trim()) {
578
- return value.message.trim();
579
- }
580
-
581
- if (!isObject(value)) {
582
- return undefined;
583
- }
584
-
585
- const directMessage = readString(value.message) ?? readString(value.errorMessage);
586
- if (directMessage) {
587
- return directMessage;
588
- }
589
-
590
- return undefined;
591
- }
592
-
593
- function extractStatus(error: unknown): number | undefined {
594
- const candidates = [
595
- error,
596
- isObject(error) ? error.error : undefined,
597
- isObject(error) ? error.response : undefined,
598
- isObject(error) && isObject(error.error) ? error.error.response : undefined,
599
- parseJsonObjectCandidate(error),
600
- ];
601
-
602
- for (const candidate of candidates) {
603
- const status = readNumericStatus(candidate);
604
- if (status !== undefined) {
605
- return status;
606
- }
607
- }
608
-
609
- return undefined;
610
- }
611
-
612
- function readNumericStatus(value: unknown): number | undefined {
613
- if (!isObject(value)) {
614
- return undefined;
615
- }
616
-
617
- const directStatus = value.status;
618
- if (typeof directStatus === "number") {
619
- return directStatus;
620
- }
621
-
622
- const statusCode = value.statusCode;
623
- if (typeof statusCode === "number") {
624
- return statusCode;
625
- }
626
-
627
- const code = value.code;
628
- if (typeof code === "number") {
629
- return code;
630
- }
631
-
632
- return undefined;
633
- }
634
-
635
- function readString(value: unknown): string | undefined {
636
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
637
- }
638
-
639
- function isObject(value: unknown): value is Record<string, unknown> {
640
- return typeof value === "object" && value !== null;
641
- }
@@ -8,12 +8,8 @@ import {
8
8
  import type { Focusable, TUI } from "@earendil-works/pi-tui";
9
9
  import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
10
10
  import type { RetitleCommandOutcome, RetitleMode, RetitleScope } from "./command.js";
11
- import {
12
- type AutoRetitleStatus,
13
- type AutoTitleFailure,
14
- formatAutoTitleFailureSummary,
15
- type SessionAutoTitleController,
16
- } from "./controller.js";
11
+ import type { AutoRetitleStatus, SessionAutoTitleController } from "./controller.js";
12
+ import type { AutoTitleFailure } from "./generate.js";
17
13
  import {
18
14
  buildBulkRetitleMessage,
19
15
  buildRetitleScopeScan,
@@ -60,6 +56,7 @@ export async function showRetitleWizard(
60
56
  ctx: ExtensionCommandContext,
61
57
  model: Model<Api> | undefined,
62
58
  getSessionEpoch: () => number,
59
+ systemPrompt: string,
63
60
  options?: RetitleWizardOptions,
64
61
  ): Promise<RetitleCommandOutcome> {
65
62
  return ctx.ui.custom<RetitleCommandOutcome>(
@@ -72,6 +69,7 @@ export async function showRetitleWizard(
72
69
  ctx,
73
70
  model,
74
71
  getSessionEpoch,
72
+ systemPrompt,
75
73
  done,
76
74
  options,
77
75
  ),
@@ -100,6 +98,7 @@ class RetitleWizardPanel implements Focusable {
100
98
  private readonly ctx: ExtensionCommandContext,
101
99
  private readonly model: Model<Api> | undefined,
102
100
  private readonly getSessionEpoch: () => number,
101
+ private readonly systemPrompt: string,
103
102
  private readonly done: (result: RetitleCommandOutcome) => void,
104
103
  options?: RetitleWizardOptions,
105
104
  ) {
@@ -250,6 +249,7 @@ class RetitleWizardPanel implements Focusable {
250
249
  ctx: this.ctx,
251
250
  model: this.model,
252
251
  isManual: true,
252
+ systemPrompt: this.systemPrompt,
253
253
  getSessionEpoch: this.getSessionEpoch,
254
254
  });
255
255
  if (result.ok) {
@@ -309,6 +309,7 @@ class RetitleWizardPanel implements Focusable {
309
309
  scan,
310
310
  mode,
311
311
  this.getSessionEpoch,
312
+ this.systemPrompt,
312
313
  );
313
314
  notifyBulkRetitleResult(this.ctx, scan, mode, result);
314
315
  this.done(
@@ -468,11 +469,7 @@ class RetitleWizardPanel implements Focusable {
468
469
  return this.renderRow(innerWidth, left);
469
470
  }
470
471
 
471
- const summaryText = truncateToWidth(
472
- formatAutoTitleFailureSummary(failure),
473
- maxSummaryWidth,
474
- "…",
475
- );
472
+ const summaryText = truncateToWidth(failure.message, maxSummaryWidth, "…");
476
473
  const right = ` ${this.theme.fg("dim", summaryText)}`;
477
474
  const gap = Math.max(1, innerWidth - visibleWidth(left) - visibleWidth(right));
478
475
  return this.renderRow(innerWidth, `${left}${" ".repeat(gap)}${right}`);
@@ -523,7 +520,6 @@ function formatAutoRetitleStatusLine(theme: Theme, status: AutoRetitleStatus): s
523
520
  function formatFailureClipboardText(failure: AutoTitleFailure): string {
524
521
  const lines = [
525
522
  `model: ${failure.model}`,
526
- ...(failure.status !== undefined ? [`status: ${failure.status}`] : []),
527
523
  `trigger: ${failure.trigger}`,
528
524
  `time: ${failure.at}`,
529
525
  `error: ${failure.message}`,
@@ -14,7 +14,6 @@ import {
14
14
  } from "./session-auto-title/command.js";
15
15
  import {
16
16
  createSessionAutoTitleController,
17
- formatAutoTitleFailureSummary,
18
17
  type SessionAutoTitleController,
19
18
  } from "./session-auto-title/controller.js";
20
19
  import { resolveAutoTitleModel } from "./session-auto-title/model.js";
@@ -26,6 +25,7 @@ import {
26
25
  runRetitlePlan,
27
26
  } from "./session-auto-title/retitle.js";
28
27
  import { showRetitleWizard } from "./session-auto-title/wizard.js";
28
+ import { isTuiMode } from "./shared/pi-mode.js";
29
29
  import { loadSettings } from "./shared/settings.js";
30
30
 
31
31
  export {
@@ -74,6 +74,7 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
74
74
  ctx,
75
75
  model,
76
76
  invocation,
77
+ settings.autoTitle.prompt,
77
78
  );
78
79
  },
79
80
  ),
@@ -102,6 +103,7 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
102
103
  existingPlan: result.plan,
103
104
  getSessionEpoch: () => sessionEpoch,
104
105
  notifyOnSuccess: false,
106
+ systemPrompt: settings.autoTitle.prompt,
105
107
  })
106
108
  .then((outcome) => {
107
109
  if (outcome.ok) {
@@ -111,7 +113,7 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
111
113
  const shouldNotify = controller.handleTitleFailed(ctx, outcome.failure);
112
114
  if (shouldNotify && ctx.hasUI) {
113
115
  ctx.ui.notify(
114
- `Auto-title failed: ${formatAutoTitleFailureSummary(outcome.failure)}. Open /title for details.`,
116
+ `Auto-title failed: ${outcome.failure.message}. Open /title for details.`,
115
117
  "warning",
116
118
  );
117
119
  }
@@ -136,6 +138,7 @@ async function handleTitleInvocation(
136
138
  ctx: ExtensionCommandContext,
137
139
  model: Model<Api> | undefined,
138
140
  invocation: RetitleCommandInvocation,
141
+ systemPrompt: string,
139
142
  ): Promise<RetitleCommandOutcome> {
140
143
  const retitleOpts = {
141
144
  pi,
@@ -143,6 +146,7 @@ async function handleTitleInvocation(
143
146
  ctx,
144
147
  model,
145
148
  isManual: true,
149
+ systemPrompt,
146
150
  getSessionEpoch: state.getSessionEpoch,
147
151
  };
148
152
 
@@ -157,24 +161,32 @@ async function handleTitleInvocation(
157
161
  };
158
162
 
159
163
  if (invocation.kind === "open-pane") {
160
- if (!ctx.hasUI) {
164
+ if (!isTuiMode(ctx)) {
161
165
  return retitleCurrentSession();
162
166
  }
163
167
 
164
- return showRetitleWizard(pi, state.controller, ctx, model, state.getSessionEpoch);
168
+ return showRetitleWizard(pi, state.controller, ctx, model, state.getSessionEpoch, systemPrompt);
165
169
  }
166
170
 
167
171
  if (invocation.scope === "this") {
168
172
  return runWithInFlightTracking(state, retitleCurrentSession);
169
173
  }
170
174
 
171
- if (ctx.hasUI && !invocation.force) {
172
- return showRetitleWizard(pi, state.controller, ctx, model, state.getSessionEpoch, {
173
- initialInvocation: {
174
- scope: invocation.scope,
175
- mode: invocation.mode ?? "backfill",
175
+ if (isTuiMode(ctx) && !invocation.force) {
176
+ return showRetitleWizard(
177
+ pi,
178
+ state.controller,
179
+ ctx,
180
+ model,
181
+ state.getSessionEpoch,
182
+ systemPrompt,
183
+ {
184
+ initialInvocation: {
185
+ scope: invocation.scope,
186
+ mode: invocation.mode ?? "backfill",
187
+ },
176
188
  },
177
- });
189
+ );
178
190
  }
179
191
 
180
192
  const scan = await buildRetitleScopeScan(ctx, invocation.scope);
@@ -188,6 +200,7 @@ async function handleTitleInvocation(
188
200
  scan,
189
201
  mode,
190
202
  state.getSessionEpoch,
203
+ systemPrompt,
191
204
  );
192
205
  notifyBulkRetitleResult(ctx, scan, mode, result);
193
206
  return "success";