@gricha/perry 0.1.5 → 0.1.7

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.
@@ -7,6 +7,7 @@ import { HOST_WORKSPACE_NAME } from '../shared/types';
7
7
  import { getDockerVersion, execInContainer } from '../docker';
8
8
  import { saveAgentConfig } from '../config/loader';
9
9
  import { setSessionName, getSessionNamesForWorkspace, deleteSessionName, } from '../sessions/metadata';
10
+ import { discoverSSHKeys } from '../ssh/discovery';
10
11
  import { parseClaudeSessionContent } from '../sessions/parser';
11
12
  import { discoverAllSessions, getSessionDetails as getAgentSessionDetails, getSessionMessages, findSessionMessages, } from '../sessions/agents';
12
13
  const WorkspaceStatusSchema = z.enum(['running', 'stopped', 'creating', 'error']);
@@ -21,6 +22,7 @@ const WorkspaceInfoSchema = z.object({
21
22
  created: z.string(),
22
23
  repo: z.string().optional(),
23
24
  ports: WorkspacePortsSchema,
25
+ lastUsed: z.string().optional(),
24
26
  });
25
27
  const CredentialsSchema = z.object({
26
28
  env: z.record(z.string(), z.string()),
@@ -47,6 +49,23 @@ const CodingAgentsSchema = z.object({
47
49
  })
48
50
  .optional(),
49
51
  });
52
+ const SSHKeyConfigSchema = z.object({
53
+ copy: z.array(z.string()),
54
+ authorize: z.array(z.string()),
55
+ });
56
+ const SSHSettingsSchema = z.object({
57
+ autoAuthorizeHostKeys: z.boolean(),
58
+ global: SSHKeyConfigSchema,
59
+ workspaces: z.record(z.string(), SSHKeyConfigSchema.partial()),
60
+ });
61
+ const SSHKeyInfoSchema = z.object({
62
+ name: z.string(),
63
+ path: z.string(),
64
+ publicKeyPath: z.string(),
65
+ type: z.enum(['ed25519', 'rsa', 'ecdsa', 'dsa', 'unknown']),
66
+ fingerprint: z.string(),
67
+ hasPrivateKey: z.boolean(),
68
+ });
50
69
  function mapErrorToORPC(err, defaultMessage) {
51
70
  const message = err instanceof Error ? err.message : defaultMessage;
52
71
  if (message.includes('not found')) {
@@ -154,6 +173,16 @@ export function createRouter(ctx) {
154
173
  results,
155
174
  };
156
175
  });
176
+ const touchWorkspace = os
177
+ .input(z.object({ name: z.string() }))
178
+ .output(WorkspaceInfoSchema)
179
+ .handler(async ({ input }) => {
180
+ const workspace = await ctx.workspaces.touch(input.name);
181
+ if (!workspace) {
182
+ throw new ORPCError('NOT_FOUND', { message: 'Workspace not found' });
183
+ }
184
+ return workspace;
185
+ });
157
186
  const getInfo = os.handler(async () => {
158
187
  let dockerVersion = 'unknown';
159
188
  try {
@@ -210,6 +239,27 @@ export function createRouter(ctx) {
210
239
  await saveAgentConfig(newConfig, ctx.configDir);
211
240
  return input;
212
241
  });
242
+ const getSSHSettings = os.output(SSHSettingsSchema).handler(async () => {
243
+ const config = ctx.config.get();
244
+ return (config.ssh || {
245
+ autoAuthorizeHostKeys: true,
246
+ global: { copy: [], authorize: [] },
247
+ workspaces: {},
248
+ });
249
+ });
250
+ const updateSSHSettings = os
251
+ .input(SSHSettingsSchema)
252
+ .output(SSHSettingsSchema)
253
+ .handler(async ({ input }) => {
254
+ const currentConfig = ctx.config.get();
255
+ const newConfig = { ...currentConfig, ssh: input };
256
+ ctx.config.set(newConfig);
257
+ await saveAgentConfig(newConfig, ctx.configDir);
258
+ return input;
259
+ });
260
+ const listSSHKeys = os.output(z.array(SSHKeyInfoSchema)).handler(async () => {
261
+ return discoverSSHKeys();
262
+ });
213
263
  async function listHostSessions(input) {
214
264
  const limit = input.limit ?? 50;
215
265
  const offset = input.offset ?? 0;
@@ -550,42 +600,23 @@ export function createRouter(ctx) {
550
600
  await deleteSessionName(ctx.stateDir, input.workspaceName, input.sessionId);
551
601
  return { success: true };
552
602
  });
553
- const listAllSessions = os
603
+ const getRecentSessions = os
554
604
  .input(z.object({
555
- agentType: z.enum(['claude-code', 'opencode', 'codex']).optional(),
556
- limit: z.number().optional().default(100),
557
- offset: z.number().optional().default(0),
605
+ limit: z.number().optional().default(10),
558
606
  }))
559
607
  .handler(async ({ input }) => {
560
- const allWorkspaces = await ctx.workspaces.list();
561
- const runningWorkspaces = allWorkspaces.filter((w) => w.status === 'running');
562
- const allSessions = [];
563
- for (const workspace of runningWorkspaces) {
564
- try {
565
- const result = await listSessionsCore({
566
- workspaceName: workspace.name,
567
- agentType: input.agentType,
568
- limit: 1000,
569
- offset: 0,
570
- });
571
- for (const session of result.sessions) {
572
- allSessions.push({
573
- ...session,
574
- workspaceName: workspace.name,
575
- });
576
- }
577
- }
578
- catch {
579
- continue;
580
- }
581
- }
582
- allSessions.sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime());
583
- const paginatedSessions = allSessions.slice(input.offset, input.offset + input.limit);
584
- return {
585
- sessions: paginatedSessions,
586
- total: allSessions.length,
587
- hasMore: input.offset + input.limit < allSessions.length,
588
- };
608
+ const recent = await ctx.sessionsCache.getRecent(input.limit);
609
+ return { sessions: recent };
610
+ });
611
+ const recordSessionAccess = os
612
+ .input(z.object({
613
+ workspaceName: z.string(),
614
+ sessionId: z.string(),
615
+ agentType: z.enum(['claude-code', 'opencode', 'codex']),
616
+ }))
617
+ .handler(async ({ input }) => {
618
+ await ctx.sessionsCache.recordAccess(input.workspaceName, input.sessionId, input.agentType);
619
+ return { success: true };
589
620
  });
590
621
  const getHostInfo = os.handler(async () => {
591
622
  const config = ctx.config.get();
@@ -621,13 +652,15 @@ export function createRouter(ctx) {
621
652
  logs: getLogs,
622
653
  sync: syncWorkspace,
623
654
  syncAll: syncAllWorkspaces,
655
+ touch: touchWorkspace,
624
656
  },
625
657
  sessions: {
626
658
  list: listSessions,
627
- listAll: listAllSessions,
628
659
  get: getSession,
629
660
  rename: renameSession,
630
661
  clearName: clearSessionName,
662
+ getRecent: getRecentSessions,
663
+ recordAccess: recordSessionAccess,
631
664
  },
632
665
  host: {
633
666
  info: getHostInfo,
@@ -647,6 +680,11 @@ export function createRouter(ctx) {
647
680
  get: getAgents,
648
681
  update: updateAgents,
649
682
  },
683
+ ssh: {
684
+ get: getSSHSettings,
685
+ update: updateSSHSettings,
686
+ listKeys: listSSHKeys,
687
+ },
650
688
  },
651
689
  };
652
690
  }
package/dist/agent/run.js CHANGED
@@ -10,6 +10,7 @@ import { ChatWebSocketServer } from '../chat/websocket';
10
10
  import { OpencodeWebSocketServer } from '../chat/opencode-websocket';
11
11
  import { createRouter } from './router';
12
12
  import { serveStatic } from './static';
13
+ import { SessionsCacheManager } from '../sessions/cache';
13
14
  import pkg from '../../package.json';
14
15
  const startTime = Date.now();
15
16
  function sendJson(res, status, data) {
@@ -19,6 +20,7 @@ function sendJson(res, status, data) {
19
20
  function createAgentServer(configDir, config) {
20
21
  let currentConfig = config;
21
22
  const workspaces = new WorkspaceManager(configDir, currentConfig);
23
+ const sessionsCache = new SessionsCacheManager(configDir);
22
24
  const isWorkspaceRunning = async (name) => {
23
25
  if (name === HOST_WORKSPACE_NAME) {
24
26
  return currentConfig.allowHostAccess === true;
@@ -52,6 +54,7 @@ function createAgentServer(configDir, config) {
52
54
  stateDir: configDir,
53
55
  startTime,
54
56
  terminalServer,
57
+ sessionsCache,
55
58
  });
56
59
  const rpcHandler = new RPCHandler(router);
57
60
  const server = createServer(async (req, res) => {