@gricha/perry 0.3.13 → 0.3.15

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.
@@ -11,6 +11,7 @@ import { setSessionName, getSessionNamesForWorkspace, deleteSessionName, } from
11
11
  import * as sessionRegistry from '../sessions/registry';
12
12
  import { discoverSSHKeys } from '../ssh/discovery';
13
13
  import { discoverAllSessions, getSessionDetails as getAgentSessionDetails, getSessionMessages, findSessionMessages, deleteSession as deleteSessionFromProvider, searchSessions as searchSessionsInContainer, } from '../sessions/agents';
14
+ import { decodeClaudeProjectPath } from '../sessions/agents/utils';
14
15
  import { discoverClaudeCodeModels, discoverHostOpencodeModels, discoverContainerOpencodeModels, } from '../models/discovery';
15
16
  import { deleteOpencodeSession } from '../sessions/agents/opencode-storage';
16
17
  import { SessionIndex } from '../worker/session-index';
@@ -435,6 +436,34 @@ export function createRouter(ctx) {
435
436
  });
436
437
  const hostSessionIndex = new SessionIndex();
437
438
  let hostSessionIndexInitialized = false;
439
+ function toRegistryAgentType(agentType) {
440
+ return agentType === 'claude-code' ? 'claude' : agentType;
441
+ }
442
+ function toClientAgentType(agentType) {
443
+ return agentType === 'claude' ? 'claude-code' : agentType;
444
+ }
445
+ async function ensureRegistrySession(workspaceName, agentType, agentSessionId, options) {
446
+ const existing = await sessionRegistry.findByAgentSessionId(ctx.stateDir, agentSessionId);
447
+ if (existing) {
448
+ return existing;
449
+ }
450
+ return sessionRegistry.importExternalSession(ctx.stateDir, {
451
+ perrySessionId: `imported-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
452
+ workspaceName,
453
+ agentType: toRegistryAgentType(agentType),
454
+ agentSessionId,
455
+ projectPath: options?.projectPath ?? null,
456
+ createdAt: options?.createdAt,
457
+ lastActivity: options?.lastActivity,
458
+ });
459
+ }
460
+ async function resolveSessionRecord(sessionId) {
461
+ const byPerry = await sessionRegistry.getSession(ctx.stateDir, sessionId);
462
+ if (byPerry) {
463
+ return byPerry;
464
+ }
465
+ return sessionRegistry.findByAgentSessionId(ctx.stateDir, sessionId);
466
+ }
438
467
  async function listHostSessions(input) {
439
468
  if (!hostSessionIndexInitialized) {
440
469
  await hostSessionIndex.initialize();
@@ -450,14 +479,27 @@ export function createRouter(ctx) {
450
479
  }
451
480
  const nonEmptySessions = sessions.filter((s) => s.messageCount > 0);
452
481
  const sessionNames = await getSessionNamesForWorkspace(ctx.configDir, HOST_WORKSPACE_NAME);
453
- const paginatedSessions = nonEmptySessions.slice(offset, offset + limit).map((s) => ({
454
- id: s.id,
455
- name: sessionNames[s.id] || null,
456
- agentType: (s.agentType === 'claude' ? 'claude-code' : s.agentType),
457
- projectPath: s.directory,
458
- messageCount: s.messageCount,
459
- lastActivity: new Date(s.lastActivity).toISOString(),
460
- firstPrompt: s.firstPrompt,
482
+ const paginatedRaw = nonEmptySessions.slice(offset, offset + limit);
483
+ const paginatedSessions = await Promise.all(paginatedRaw.map(async (session) => {
484
+ const projectPath = session.agentType === 'claude'
485
+ ? decodeClaudeProjectPath(session.directory)
486
+ : session.directory;
487
+ const record = await ensureRegistrySession(HOST_WORKSPACE_NAME, session.agentType, session.id, {
488
+ projectPath,
489
+ createdAt: new Date(session.lastActivity).toISOString(),
490
+ lastActivity: new Date(session.lastActivity).toISOString(),
491
+ });
492
+ const name = sessionNames[record.perrySessionId] || sessionNames[session.id] || null;
493
+ return {
494
+ id: record.perrySessionId,
495
+ agentSessionId: session.id,
496
+ name,
497
+ agentType: toClientAgentType(session.agentType),
498
+ projectPath,
499
+ messageCount: session.messageCount,
500
+ lastActivity: new Date(session.lastActivity).toISOString(),
501
+ firstPrompt: session.firstPrompt,
502
+ };
461
503
  }));
462
504
  return {
463
505
  sessions: paginatedSessions,
@@ -471,12 +513,14 @@ export function createRouter(ctx) {
471
513
  hostSessionIndex.startWatchers();
472
514
  hostSessionIndexInitialized = true;
473
515
  }
474
- const session = hostSessionIndex.get(sessionId);
516
+ const record = await resolveSessionRecord(sessionId);
517
+ const agentSessionId = record?.agentSessionId || sessionId;
518
+ const session = hostSessionIndex.get(agentSessionId);
475
519
  if (!session) {
476
520
  return { id: sessionId, messages: [] };
477
521
  }
478
- const result = await hostSessionIndex.getMessages(sessionId, { limit: 10000, offset: 0 });
479
- const agentType = session.agentType === 'claude' ? 'claude-code' : session.agentType;
522
+ const result = await hostSessionIndex.getMessages(agentSessionId, { limit: 10000, offset: 0 });
523
+ const agentType = toClientAgentType(session.agentType);
480
524
  const messages = result.messages.map((m) => ({
481
525
  type: m.type,
482
526
  content: m.content,
@@ -485,7 +529,13 @@ export function createRouter(ctx) {
485
529
  toolInput: m.toolInput,
486
530
  timestamp: m.timestamp,
487
531
  }));
488
- return { id: sessionId, agentType, messages };
532
+ const ensured = record ||
533
+ (await ensureRegistrySession(HOST_WORKSPACE_NAME, session.agentType, agentSessionId, {
534
+ projectPath: session.agentType === 'claude'
535
+ ? decodeClaudeProjectPath(session.directory)
536
+ : session.directory,
537
+ }));
538
+ return { id: ensured.perrySessionId, agentType, messages, agentSessionId };
489
539
  }
490
540
  async function listSessionsCore(input) {
491
541
  const limit = input.limit ?? 50;
@@ -508,20 +558,13 @@ export function createRouter(ctx) {
508
558
  const containerName = `workspace-${input.workspaceName}`;
509
559
  const rawSessions = await discoverAllSessions(containerName, execInContainer);
510
560
  const customNames = await getSessionNamesForWorkspace(ctx.stateDir, input.workspaceName);
511
- const registrySessions = await sessionRegistry.getSessionsForWorkspace(ctx.stateDir, input.workspaceName);
512
- const trackedAgentIds = new Set(registrySessions.filter((s) => s.agentSessionId).map((s) => s.agentSessionId));
561
+ const registryByAgentId = new Map();
513
562
  for (const raw of rawSessions) {
514
- if (!trackedAgentIds.has(raw.id)) {
515
- const agentType = raw.agentType === 'claude-code' ? 'claude' : raw.agentType;
516
- await sessionRegistry.importExternalSession(ctx.stateDir, {
517
- perrySessionId: `imported-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
518
- workspaceName: input.workspaceName,
519
- agentType: agentType,
520
- agentSessionId: raw.id,
521
- createdAt: new Date(raw.mtime).toISOString(),
522
- lastActivity: new Date(raw.mtime).toISOString(),
523
- });
524
- }
563
+ const record = await ensureRegistrySession(input.workspaceName, raw.agentType, raw.id, {
564
+ createdAt: new Date(raw.mtime).toISOString(),
565
+ lastActivity: new Date(raw.mtime).toISOString(),
566
+ });
567
+ registryByAgentId.set(raw.id, record);
525
568
  }
526
569
  const filteredSessions = rawSessions
527
570
  .filter((s) => !input.agentType || s.agentType === input.agentType)
@@ -530,10 +573,21 @@ export function createRouter(ctx) {
530
573
  const detailsResults = await Promise.all(paginatedRawSessions.map((rawSession) => getAgentSessionDetails(containerName, rawSession, execInContainer)));
531
574
  const sessions = detailsResults
532
575
  .filter((details) => details !== null)
533
- .map((details) => ({
534
- ...details,
535
- name: customNames[details.id] || details.name,
536
- }));
576
+ .map((details) => {
577
+ const record = registryByAgentId.get(details.id);
578
+ const perryId = record?.perrySessionId || details.id;
579
+ const name = customNames[perryId] || customNames[details.id] || details.name;
580
+ const projectPath = details.agentType === 'claude-code'
581
+ ? decodeClaudeProjectPath(details.projectPath)
582
+ : details.projectPath;
583
+ return {
584
+ ...details,
585
+ id: perryId,
586
+ agentSessionId: details.id,
587
+ name,
588
+ projectPath,
589
+ };
590
+ });
537
591
  return {
538
592
  sessions,
539
593
  total: filteredSessions.length,
@@ -578,9 +632,35 @@ export function createRouter(ctx) {
578
632
  throw new ORPCError('PRECONDITION_FAILED', { message: 'Workspace is not running' });
579
633
  }
580
634
  const containerName = `workspace-${input.workspaceName}`;
581
- result = input.agentType
582
- ? await getSessionMessages(containerName, input.sessionId, input.agentType, execInContainer, input.projectPath)
583
- : await findSessionMessages(containerName, input.sessionId, execInContainer);
635
+ const record = await resolveSessionRecord(input.sessionId);
636
+ if (record && !record.agentSessionId) {
637
+ throw new ORPCError('NOT_FOUND', { message: 'Session not found' });
638
+ }
639
+ const agentSessionId = record?.agentSessionId || input.sessionId;
640
+ const resolvedAgentType = record?.agentType
641
+ ? toClientAgentType(record.agentType)
642
+ : input.agentType;
643
+ result = resolvedAgentType
644
+ ? await getSessionMessages(containerName, agentSessionId, resolvedAgentType, execInContainer, input.projectPath)
645
+ : await findSessionMessages(containerName, agentSessionId, execInContainer);
646
+ if (result && !record) {
647
+ const agentType = toRegistryAgentType(result.agentType || resolvedAgentType);
648
+ const created = await ensureRegistrySession(input.workspaceName, agentType, result.id, {
649
+ projectPath: input.projectPath,
650
+ });
651
+ result = {
652
+ ...result,
653
+ id: created.perrySessionId,
654
+ agentSessionId: result.id,
655
+ };
656
+ }
657
+ else if (result && record) {
658
+ result = {
659
+ ...result,
660
+ id: record.perrySessionId,
661
+ agentSessionId: record.agentSessionId || agentSessionId,
662
+ };
663
+ }
584
664
  }
585
665
  if (!result) {
586
666
  throw new ORPCError('NOT_FOUND', { message: 'Session not found' });
@@ -651,14 +731,18 @@ export function createRouter(ctx) {
651
731
  if (!config.allowHostAccess) {
652
732
  throw new ORPCError('PRECONDITION_FAILED', { message: 'Host access is disabled' });
653
733
  }
654
- const result = await deleteHostSession(input.sessionId, input.agentType);
734
+ const record = await resolveSessionRecord(input.sessionId);
735
+ const agentSessionId = record?.agentSessionId || input.sessionId;
736
+ const agentType = record?.agentType ? toClientAgentType(record.agentType) : input.agentType;
737
+ const result = await deleteHostSession(agentSessionId, agentType);
655
738
  if (!result.success) {
656
739
  throw new ORPCError('INTERNAL_SERVER_ERROR', {
657
740
  message: result.error || 'Failed to delete session',
658
741
  });
659
742
  }
660
- await deleteSessionName(ctx.stateDir, input.workspaceName, input.sessionId);
661
- await ctx.sessionsCache.removeSession(input.workspaceName, input.sessionId);
743
+ const perryId = record?.perrySessionId || input.sessionId;
744
+ await deleteSessionName(ctx.stateDir, input.workspaceName, perryId);
745
+ await ctx.sessionsCache.removeSession(input.workspaceName, perryId);
662
746
  return { success: true };
663
747
  }
664
748
  const workspace = await ctx.workspaces.get(input.workspaceName);
@@ -669,14 +753,18 @@ export function createRouter(ctx) {
669
753
  throw new ORPCError('PRECONDITION_FAILED', { message: 'Workspace is not running' });
670
754
  }
671
755
  const containerName = `workspace-${input.workspaceName}`;
672
- const result = await deleteSessionFromProvider(containerName, input.sessionId, input.agentType, execInContainer);
756
+ const record = await resolveSessionRecord(input.sessionId);
757
+ const agentSessionId = record?.agentSessionId || input.sessionId;
758
+ const agentType = record?.agentType ? toClientAgentType(record.agentType) : input.agentType;
759
+ const result = await deleteSessionFromProvider(containerName, agentSessionId, agentType, execInContainer);
673
760
  if (!result.success) {
674
761
  throw new ORPCError('INTERNAL_SERVER_ERROR', {
675
762
  message: result.error || 'Failed to delete session',
676
763
  });
677
764
  }
678
- await deleteSessionName(ctx.stateDir, input.workspaceName, input.sessionId);
679
- await ctx.sessionsCache.removeSession(input.workspaceName, input.sessionId);
765
+ const perryId = record?.perrySessionId || input.sessionId;
766
+ await deleteSessionName(ctx.stateDir, input.workspaceName, perryId);
767
+ await ctx.sessionsCache.removeSession(input.workspaceName, perryId);
680
768
  return { success: true };
681
769
  });
682
770
  const searchSessions = os
@@ -702,7 +790,15 @@ export function createRouter(ctx) {
702
790
  throw new ORPCError('PRECONDITION_FAILED', { message: 'Workspace is not running' });
703
791
  }
704
792
  const containerName = `workspace-${input.workspaceName}`;
705
- const results = await searchSessionsInContainer(containerName, input.query, execInContainer);
793
+ const rawResults = await searchSessionsInContainer(containerName, input.query, execInContainer);
794
+ const results = await Promise.all(rawResults.map(async (result) => {
795
+ const record = await ensureRegistrySession(input.workspaceName, result.agentType, result.sessionId);
796
+ return {
797
+ ...result,
798
+ sessionId: record.perrySessionId,
799
+ agentSessionId: result.sessionId,
800
+ };
801
+ }));
706
802
  return { results };
707
803
  });
708
804
  async function searchHostSessions(query) {
@@ -759,7 +855,13 @@ export function createRouter(ctx) {
759
855
  }
760
856
  }
761
857
  if (sessionId && agentType) {
762
- results.push({ sessionId, agentType, matchCount: 1 });
858
+ const record = await ensureRegistrySession(HOST_WORKSPACE_NAME, agentType, sessionId);
859
+ results.push({
860
+ sessionId: record.perrySessionId,
861
+ agentSessionId: sessionId,
862
+ agentType,
863
+ matchCount: 1,
864
+ });
763
865
  }
764
866
  }
765
867
  return results;