subconscious-cli 6.0.0 → 6.0.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.
@@ -31,40 +31,43 @@
31
31
  * https://pi.dev/docs/latest/extensions
32
32
  */
33
33
 
34
- import { readFileSync } from "node:fs"
35
- import { homedir } from "node:os"
36
- import { join } from "node:path"
34
+ import { readFileSync } from 'node:fs';
35
+ import { homedir } from 'node:os';
36
+ import { join } from 'node:path';
37
37
 
38
- const TIMEOUT_MS = 2000
38
+ const TIMEOUT_MS = 2000;
39
39
 
40
- type CompactPhase = "start" | "end"
40
+ type CompactPhase = 'start' | 'end';
41
41
 
42
- let envFileLoaded = false
42
+ let envFileLoaded = false;
43
43
 
44
44
  function loadSubconsciousEnvFile(): void {
45
- if (envFileLoaded) return
46
- envFileLoaded = true
47
- if (process.env.SUBCONSCIOUS_GATEWAY_URL && process.env.SUBCONSCIOUS_API_KEY) {
48
- return
45
+ if (envFileLoaded) return;
46
+ envFileLoaded = true;
47
+ if (
48
+ process.env.SUBCONSCIOUS_GATEWAY_URL &&
49
+ process.env.SUBCONSCIOUS_API_KEY
50
+ ) {
51
+ return;
49
52
  }
50
53
  try {
51
- const path = join(homedir(), ".pi", "agent", "subconscious.env")
52
- const text = readFileSync(path, "utf8")
53
- for (const line of text.split("\n")) {
54
- const trimmed = line.trim()
55
- if (!trimmed || trimmed.startsWith("#")) continue
56
- const m = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/)
57
- if (!m) continue
58
- const key = m[1]
59
- let val = m[2]
54
+ const path = join(homedir(), '.pi', 'agent', 'subconscious.env');
55
+ const text = readFileSync(path, 'utf8');
56
+ for (const line of text.split('\n')) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed || trimmed.startsWith('#')) continue;
59
+ const m = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
60
+ if (!m) continue;
61
+ const key = m[1];
62
+ let val = m[2];
60
63
  if (
61
64
  (val.startsWith('"') && val.endsWith('"')) ||
62
65
  (val.startsWith("'") && val.endsWith("'"))
63
66
  ) {
64
- val = val.slice(1, -1)
67
+ val = val.slice(1, -1);
65
68
  }
66
69
  if (process.env[key] === undefined) {
67
- process.env[key] = val
70
+ process.env[key] = val;
68
71
  }
69
72
  }
70
73
  } catch {
@@ -73,14 +76,18 @@ function loadSubconsciousEnvFile(): void {
73
76
  }
74
77
 
75
78
  function gatewayUrl(): string | undefined {
76
- loadSubconsciousEnvFile()
77
- const raw = process.env.SUBCONSCIOUS_GATEWAY_URL ?? process.env.GATEWAY_URL
78
- return raw ? raw.replace(/\/+$/, "") : undefined
79
+ loadSubconsciousEnvFile();
80
+ const raw = process.env.SUBCONSCIOUS_GATEWAY_URL ?? process.env.GATEWAY_URL;
81
+ return raw ? raw.replace(/\/+$/, '') : undefined;
79
82
  }
80
83
 
81
84
  function apiKey(): string | undefined {
82
- loadSubconsciousEnvFile()
83
- return process.env.SUBCONSCIOUS_API_KEY ?? process.env.API_KEY ?? process.env.PI_API_KEY
85
+ loadSubconsciousEnvFile();
86
+ return (
87
+ process.env.SUBCONSCIOUS_API_KEY ??
88
+ process.env.API_KEY ??
89
+ process.env.PI_API_KEY
90
+ );
84
91
  }
85
92
 
86
93
  async function report(
@@ -89,25 +96,25 @@ async function report(
89
96
  hookEventName: string,
90
97
  metadata?: Record<string, unknown>,
91
98
  ): Promise<void> {
92
- const url = gatewayUrl()
93
- const key = apiKey()
94
- if (!url || !key || !sessionID) return
99
+ const url = gatewayUrl();
100
+ const key = apiKey();
101
+ if (!url || !key || !sessionID) return;
95
102
 
96
103
  // Unique per event, stable for a transport-level retry of this same POST.
97
- const dedupeKey = `${sessionID}:${phase}:${Date.now()}`
104
+ const dedupeKey = `${sessionID}:${phase}:${Date.now()}`;
98
105
 
99
- const controller = new AbortController()
100
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)
106
+ const controller = new AbortController();
107
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
101
108
  try {
102
109
  await fetch(`${url}/v1/agent-hooks`, {
103
- method: "POST",
110
+ method: 'POST',
104
111
  headers: {
105
- "content-type": "application/json",
112
+ 'content-type': 'application/json',
106
113
  authorization: `Bearer ${key}`,
107
- "x-subconscious-client": "pi",
114
+ 'x-subconscious-client': 'pi',
108
115
  },
109
116
  body: JSON.stringify({
110
- event: "conversation_compaction",
117
+ event: 'conversation_compaction',
111
118
  conversation_id: sessionID,
112
119
  phase,
113
120
  hook_event_name: hookEventName,
@@ -115,37 +122,50 @@ async function report(
115
122
  metadata: metadata ?? undefined,
116
123
  }),
117
124
  signal: controller.signal,
118
- })
125
+ });
119
126
  } catch {
120
127
  // Fail open on timeout, offline gateway, or auth failure.
121
128
  } finally {
122
- clearTimeout(timer)
129
+ clearTimeout(timer);
123
130
  }
124
131
  }
125
132
 
133
+ type PiCompactEvent = {
134
+ reason?: unknown;
135
+ preparation?: { tokensBefore?: unknown };
136
+ fromExtension?: unknown;
137
+ };
138
+
139
+ type PiContext = {
140
+ sessionManager?: { getSessionId?: () => string | undefined };
141
+ };
142
+
126
143
  // Pi auto-discovers default-exported extension factories from
127
144
  // ~/.pi/agent/extensions/*.ts. See https://pi.dev/docs/latest/extensions
128
145
  export default function (pi: {
129
- on: (event: string, handler: (...args: any[]) => any) => void
146
+ on: (
147
+ event: string,
148
+ handler: (event: PiCompactEvent, ctx: PiContext) => Promise<void>,
149
+ ) => void;
130
150
  }) {
131
- pi.on("session_before_compact", async (event: any, ctx: any) => {
132
- const sessionID = ctx?.sessionManager?.getSessionId?.()
151
+ pi.on('session_before_compact', async (event, ctx) => {
152
+ const sessionID = ctx?.sessionManager?.getSessionId?.();
133
153
  if (sessionID) {
134
- await report(sessionID, "start", "session_before_compact", {
154
+ await report(sessionID, 'start', 'session_before_compact', {
135
155
  reason: event?.reason ?? null,
136
156
  tokens_before: event?.preparation?.tokensBefore ?? null,
137
- })
157
+ });
138
158
  }
139
159
  // Deliberately return nothing: do not cancel or replace summarization.
140
- })
160
+ });
141
161
 
142
- pi.on("session_compact", async (event: any, ctx: any) => {
143
- const sessionID = ctx?.sessionManager?.getSessionId?.()
162
+ pi.on('session_compact', async (event, ctx) => {
163
+ const sessionID = ctx?.sessionManager?.getSessionId?.();
144
164
  if (sessionID) {
145
- await report(sessionID, "end", "session_compact", {
165
+ await report(sessionID, 'end', 'session_compact', {
146
166
  reason: event?.reason ?? null,
147
167
  from_extension: event?.fromExtension ?? null,
148
- })
168
+ });
149
169
  }
150
- })
170
+ });
151
171
  }
package/bin/sessions.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { spawnWindowsSync } from './windows/process.js';
3
2
  import fs from 'node:fs/promises';
4
3
  import os from 'node:os';
5
4
  import path from 'node:path';
5
+ import { resolveAgent, runAgent } from './agents.js';
6
6
 
7
7
  import { c } from './colors.js';
8
- import { resolveAgent, runAgent } from './agents.js';
8
+ import { spawnWindowsSync } from './windows/process.js';
9
9
 
10
10
  const MAX_CANDIDATE_FILES = 160;
11
11
  const MAX_READ_BYTES = 2 * 1024 * 1024;
@@ -26,7 +26,7 @@ export const SESSION_HARNESSES = Object.freeze({
26
26
 
27
27
  function cleanText(value) {
28
28
  return String(value || '')
29
- .replace(/\u0000/g, '')
29
+ .replaceAll('\0', '')
30
30
  .replace(/\r\n/g, '\n')
31
31
  .trim();
32
32
  }
@@ -194,8 +194,15 @@ function codexMessages(records) {
194
194
  return records.flatMap((record) => {
195
195
  if (record.type !== 'event_msg') return [];
196
196
  const eventType = record.payload?.type;
197
- const role = eventType === 'user_message' ? 'user' : eventType === 'agent_message' ? 'assistant' : '';
198
- const text = cleanText(record.payload?.message || record.payload?.last_agent_message);
197
+ const role =
198
+ eventType === 'user_message'
199
+ ? 'user'
200
+ : eventType === 'agent_message'
201
+ ? 'assistant'
202
+ : '';
203
+ const text = cleanText(
204
+ record.payload?.message || record.payload?.last_agent_message,
205
+ );
199
206
  return role && text ? [{ role, text }] : [];
200
207
  });
201
208
  }
@@ -227,12 +234,22 @@ async function parseFileSession(entry, harness) {
227
234
  const messages = claudeMessages(records);
228
235
  const metadata = records.find((record) => record.cwd) || {};
229
236
  const identity = records.find((record) => record.sessionId) || {};
230
- const id = metadata.sessionId || identity.sessionId || filename.split('.orphaned-')[0];
237
+ const id =
238
+ metadata.sessionId ||
239
+ identity.sessionId ||
240
+ filename.split('.orphaned-')[0];
231
241
  const titleRecord = records.find((record) => record.type === 'ai-title');
232
- const lastPrompt = [...records].reverse().find((record) => record.type === 'last-prompt');
233
- const assistant = records.find((record) => record.message?.role === 'assistant');
242
+ const lastPrompt = [...records]
243
+ .reverse()
244
+ .find((record) => record.type === 'last-prompt');
245
+ const assistant = records.find(
246
+ (record) => record.message?.role === 'assistant',
247
+ );
234
248
  return sessionRecord(harness, id, {
235
- title: titleRecord?.aiTitle || messages.find((message) => message.role === 'user')?.text || lastPrompt?.lastPrompt,
249
+ title:
250
+ titleRecord?.aiTitle ||
251
+ messages.find((message) => message.role === 'user')?.text ||
252
+ lastPrompt?.lastPrompt,
236
253
  cwd: metadata.cwd,
237
254
  model: assistant?.message?.model,
238
255
  updated: entry.updated,
@@ -241,10 +258,14 @@ async function parseFileSession(entry, harness) {
241
258
  }
242
259
 
243
260
  if (harness === 'codex') {
244
- const meta = records.find((record) => record.type === 'session_meta')?.payload || {};
261
+ const meta =
262
+ records.find((record) => record.type === 'session_meta')?.payload || {};
245
263
  const messages = codexMessages(records);
246
- const id = meta.id || filename.match(/([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})$/i)?.[1];
247
- const context = records.find((record) => record.type === 'turn_context')?.payload || {};
264
+ const id =
265
+ meta.id ||
266
+ filename.match(/([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})$/i)?.[1];
267
+ const context =
268
+ records.find((record) => record.type === 'turn_context')?.payload || {};
248
269
  return sessionRecord(harness, id, {
249
270
  title: messages.find((message) => message.role === 'user')?.text,
250
271
  cwd: meta.cwd,
@@ -257,7 +278,9 @@ async function parseFileSession(entry, harness) {
257
278
  if (harness === 'pi') {
258
279
  const meta = records.find((record) => record.type === 'session') || {};
259
280
  const messages = piMessages(records);
260
- const assistant = records.find((record) => record.message?.role === 'assistant');
281
+ const assistant = records.find(
282
+ (record) => record.message?.role === 'assistant',
283
+ );
261
284
  return sessionRecord(harness, meta.id || filename.split('_').at(-1), {
262
285
  title: messages.find((message) => message.role === 'user')?.text,
263
286
  cwd: meta.cwd,
@@ -294,11 +317,17 @@ async function discoverFileSessions(root, harness, limit) {
294
317
  function sessionIdFromFile(file, harness) {
295
318
  const name = path.basename(file, '.jsonl');
296
319
  if (harness === 'claude') {
297
- if (name.includes('.orphaned-') || file.split(path.sep).includes('subagents')) return '';
320
+ if (
321
+ name.includes('.orphaned-') ||
322
+ file.split(path.sep).includes('subagents')
323
+ )
324
+ return '';
298
325
  return name;
299
326
  }
300
327
  if (harness === 'codex') {
301
- return name.match(/([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})$/i)?.[1] || '';
328
+ return (
329
+ name.match(/([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})$/i)?.[1] || ''
330
+ );
302
331
  }
303
332
  return '';
304
333
  }
@@ -359,10 +388,16 @@ async function discoverCodexIndex(root, indexFile, max) {
359
388
  const updated = Date.parse(record.updated_at || '') || 0;
360
389
  const current = byID.get(record.id);
361
390
  if (!current || updated >= current.updated) {
362
- byID.set(record.id, { id: record.id, title: record.thread_name, updated });
391
+ byID.set(record.id, {
392
+ id: record.id,
393
+ title: record.thread_name,
394
+ updated,
395
+ });
363
396
  }
364
397
  }
365
- const selected = [...byID.values()].sort((a, b) => b.updated - a.updated).slice(0, max);
398
+ const selected = [...byID.values()]
399
+ .sort((a, b) => b.updated - a.updated)
400
+ .slice(0, max);
366
401
  return selected.map((record) =>
367
402
  sessionRecord('codex', record.id, {
368
403
  ...record,
@@ -400,7 +435,12 @@ function discoverOpenCodeSessions(execute, max) {
400
435
  'from session where time_archived is null',
401
436
  `order by time_updated desc limit ${Math.max(1, Math.min(500, max))}`,
402
437
  ].join(' ');
403
- const output = commandResult(execute, 'opencode', ['db', '--format', 'json', query]);
438
+ const output = commandResult(execute, 'opencode', [
439
+ 'db',
440
+ '--format',
441
+ 'json',
442
+ query,
443
+ ]);
404
444
  if (!output) return [];
405
445
  try {
406
446
  return JSON.parse(output).map((record) =>
@@ -419,7 +459,9 @@ function discoverOpenCodeSessions(execute, max) {
419
459
  export async function discoverSessions(options = {}) {
420
460
  const home = options.home || os.homedir();
421
461
  const max = options.max || 500;
422
- const execute = options.execute || (process.platform === 'win32' ? spawnWindowsSync : spawnSync);
462
+ const execute =
463
+ options.execute ||
464
+ (process.platform === 'win32' ? spawnWindowsSync : spawnSync);
423
465
  const roots = options.roots || {
424
466
  claude: path.join(home, '.claude', 'projects'),
425
467
  codex: path.join(home, '.codex', 'sessions'),
@@ -467,11 +509,19 @@ async function openCodeMessages(session, execute) {
467
509
  "and json_extract(part.data, '$.type') = 'text'",
468
510
  'order by message.time_created asc, part.time_created asc',
469
511
  ].join(' ');
470
- const output = commandResult(execute, 'opencode', ['db', '--format', 'json', query]);
512
+ const output = commandResult(execute, 'opencode', [
513
+ 'db',
514
+ '--format',
515
+ 'json',
516
+ query,
517
+ ]);
471
518
  if (!output) return [];
472
519
  try {
473
520
  return JSON.parse(output)
474
- .filter((row) => ['user', 'assistant'].includes(row.role) && cleanText(row.text))
521
+ .filter(
522
+ (row) =>
523
+ ['user', 'assistant'].includes(row.role) && cleanText(row.text),
524
+ )
475
525
  .map((row) => ({ role: row.role, text: cleanText(row.text) }));
476
526
  } catch {
477
527
  return [];
@@ -480,7 +530,11 @@ async function openCodeMessages(session, execute) {
480
530
 
481
531
  export async function readSessionMessages(session, options = {}) {
482
532
  if (session.harness === 'opencode') {
483
- return openCodeMessages(session, options.execute || (process.platform === 'win32' ? spawnWindowsSync : spawnSync));
533
+ return openCodeMessages(
534
+ session,
535
+ options.execute ||
536
+ (process.platform === 'win32' ? spawnWindowsSync : spawnSync),
537
+ );
484
538
  }
485
539
  if (!session.sourcePath) return [];
486
540
  const records = parseLines(await readBounded(session.sourcePath));
@@ -492,11 +546,14 @@ export async function readSessionMessages(session, options = {}) {
492
546
  }
493
547
 
494
548
  async function hydrateSession(session) {
495
- if (session.harness !== 'codex' || !session.sourcePath || session.cwd) return session;
549
+ if (session.harness !== 'codex' || !session.sourcePath || session.cwd)
550
+ return session;
496
551
  try {
497
552
  const head = parseLines(await readHead(session.sourcePath));
498
- const meta = head.find((item) => item.type === 'session_meta')?.payload || {};
499
- const context = head.find((item) => item.type === 'turn_context')?.payload || {};
553
+ const meta =
554
+ head.find((item) => item.type === 'session_meta')?.payload || {};
555
+ const context =
556
+ head.find((item) => item.type === 'turn_context')?.payload || {};
500
557
  return { ...session, cwd: meta.cwd || '', model: context.model || '' };
501
558
  } catch {
502
559
  return session;
@@ -518,7 +575,9 @@ export function buildHandoffPrompt(session, messages) {
518
575
  }
519
576
  selected.reverse();
520
577
  if (!selected.length) {
521
- throw new Error(`No portable user/assistant messages were found in ${session.harnessName}.`);
578
+ throw new Error(
579
+ `No portable user/assistant messages were found in ${session.harnessName}.`,
580
+ );
522
581
  }
523
582
 
524
583
  return [
@@ -539,7 +598,8 @@ export function nativeResumeArgs(session) {
539
598
  if (session.harness === 'claude') return ['--resume', session.id];
540
599
  if (session.harness === 'codex') return ['resume', session.id];
541
600
  if (session.harness === 'opencode') return ['--session', session.id];
542
- if (session.harness === 'pi') return ['--session', session.sourcePath || session.id];
601
+ if (session.harness === 'pi')
602
+ return ['--session', session.sourcePath || session.id];
543
603
  if (session.harness === 'sc') return ['--resume', session.sourcePath];
544
604
  throw new Error(`Native resume is not supported for ${session.harnessName}.`);
545
605
  }
@@ -547,11 +607,14 @@ export function nativeResumeArgs(session) {
547
607
  export function handoffLaunchArgs(targetHarness, prompt) {
548
608
  if (targetHarness === 'opencode') return ['--prompt', prompt];
549
609
  if (['claude', 'codex', 'pi'].includes(targetHarness)) return [prompt];
550
- throw new Error(`${SESSION_HARNESSES[targetHarness]?.name || targetHarness} cannot start an interactive handoff yet.`);
610
+ throw new Error(
611
+ `${SESSION_HARNESSES[targetHarness]?.name || targetHarness} cannot start an interactive handoff yet.`,
612
+ );
551
613
  }
552
614
 
553
615
  function sessionByKey(sessions, key) {
554
- if (!SESSION_KEY_PATTERN.test(key || '')) throw new Error('Invalid session key.');
616
+ if (!SESSION_KEY_PATTERN.test(key || ''))
617
+ throw new Error('Invalid session key.');
555
618
  return sessions.find((session) => session.key === key) || null;
556
619
  }
557
620
 
@@ -564,11 +627,15 @@ function formatWhen(timestamp) {
564
627
  export function printSessions(sessions) {
565
628
  console.log(`\n ${c.bold}Coding sessions${c.reset}\n`);
566
629
  if (!sessions.length) {
567
- console.log(` ${c.dim}No supported local coding sessions were found.${c.reset}\n`);
630
+ console.log(
631
+ ` ${c.dim}No supported local coding sessions were found.${c.reset}\n`,
632
+ );
568
633
  return;
569
634
  }
570
635
  for (const session of sessions) {
571
- console.log(` ${c.cyan}${session.key}${c.reset} ${c.bold}${session.title}${c.reset}`);
636
+ console.log(
637
+ ` ${c.cyan}${session.key}${c.reset} ${c.bold}${session.title}${c.reset}`,
638
+ );
572
639
  console.log(
573
640
  ` ${c.dim}${session.harnessName} · ${formatWhen(session.updatedAt)}${session.cwd ? ` · ${session.cwd}` : ''}${c.reset}`,
574
641
  );
@@ -585,15 +652,21 @@ export async function sessionsCommand(argv, options = {}) {
585
652
  printSessions(sessions);
586
653
  return 0;
587
654
  }
588
- if (action !== 'resume') throw new Error(`Unknown sessions action: ${action}`);
655
+ if (action !== 'resume')
656
+ throw new Error(`Unknown sessions action: ${action}`);
589
657
 
590
658
  const key = argv[1];
591
659
  let session = sessionByKey(sessions, key);
592
- if (!session) throw new Error(`Session '${key}' was not found. Run subc sessions to refresh the list.`);
660
+ if (!session)
661
+ throw new Error(
662
+ `Session '${key}' was not found. Run subc sessions to refresh the list.`,
663
+ );
593
664
  session = await hydrateSession(session);
594
665
  const harnessIndex = argv.indexOf('--harness');
595
- const requestedHarness = harnessIndex >= 0 ? argv[harnessIndex + 1] : session.harness;
596
- const targetHarness = requestedHarness === 'marathon' ? 'sc' : requestedHarness;
666
+ const requestedHarness =
667
+ harnessIndex >= 0 ? argv[harnessIndex + 1] : session.harness;
668
+ const targetHarness =
669
+ requestedHarness === 'marathon' ? 'sc' : requestedHarness;
597
670
  const target = SESSION_HARNESSES[targetHarness];
598
671
  if (!target) throw new Error(`Unknown destination harness: ${targetHarness}`);
599
672
 
@@ -602,22 +675,34 @@ export async function sessionsCommand(argv, options = {}) {
602
675
  const stat = await fs.stat(session.cwd);
603
676
  if (stat.isDirectory()) process.chdir(session.cwd);
604
677
  } catch {
605
- console.error(` ${c.yellow}The original directory is unavailable; resuming in ${process.cwd()}.${c.reset}\n`);
678
+ console.error(
679
+ ` ${c.yellow}The original directory is unavailable; resuming in ${process.cwd()}.${c.reset}\n`,
680
+ );
606
681
  }
607
682
  }
608
683
 
609
684
  const agent = resolveAgent(target.command);
610
- if (!agent) throw new Error(`${target.name} is not registered with this CLI.`);
685
+ if (!agent)
686
+ throw new Error(`${target.name} is not registered with this CLI.`);
611
687
  if (targetHarness === session.harness) {
612
- console.log(`\n ${c.dim}Resuming in ${c.reset}${c.bold}${session.harnessName}${c.reset}${c.dim}: ${session.title}${c.reset}\n`);
613
- return runAgent(agent, nativeResumeArgs(session), { profile: options.profile });
688
+ console.log(
689
+ `\n ${c.dim}Resuming in ${c.reset}${c.bold}${session.harnessName}${c.reset}${c.dim}: ${session.title}${c.reset}\n`,
690
+ );
691
+ return runAgent(agent, nativeResumeArgs(session), {
692
+ profile: options.profile,
693
+ });
614
694
  }
615
- if (!target.portable) throw new Error(`${target.name} cannot receive cross-harness sessions yet.`);
695
+ if (!target.portable)
696
+ throw new Error(
697
+ `${target.name} cannot receive cross-harness sessions yet.`,
698
+ );
616
699
 
617
700
  const messages = await readSessionMessages(session, options);
618
701
  const prompt = buildHandoffPrompt(session, messages);
619
702
  console.log(
620
703
  `\n ${c.dim}Handing off ${c.reset}${c.bold}${session.harnessName}${c.reset}${c.dim} → ${c.reset}${c.bold}${target.name}${c.reset}${c.dim}: ${session.title}${c.reset}\n`,
621
704
  );
622
- return runAgent(agent, handoffLaunchArgs(targetHarness, prompt), { profile: options.profile });
705
+ return runAgent(agent, handoffLaunchArgs(targetHarness, prompt), {
706
+ profile: options.profile,
707
+ });
623
708
  }