@gricha/perry 0.2.1 → 0.2.3
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.
- package/README.md +26 -2
- package/dist/agent/file-watcher.js +138 -0
- package/dist/agent/router.js +231 -88
- package/dist/agent/run.js +81 -6
- package/dist/agent/web/assets/index-BF-4SpMu.js +104 -0
- package/dist/agent/web/assets/index-DIOWcVH-.css +1 -0
- package/dist/agent/web/index.html +2 -2
- package/dist/chat/base-chat-websocket.js +1 -1
- package/dist/chat/base-claude-session.js +169 -0
- package/dist/chat/base-opencode-session.js +181 -0
- package/dist/chat/handler.js +14 -157
- package/dist/chat/host-handler.js +13 -142
- package/dist/chat/host-opencode-handler.js +28 -187
- package/dist/chat/opencode-handler.js +38 -197
- package/dist/chat/types.js +1 -0
- package/dist/docker/index.js +1 -1
- package/dist/index.js +42 -0
- package/dist/perry-worker +0 -0
- package/dist/sessions/agents/claude.js +19 -0
- package/dist/sessions/agents/codex.js +40 -0
- package/dist/sessions/agents/index.js +63 -0
- package/dist/sessions/agents/opencode-storage.js +218 -0
- package/dist/sessions/agents/opencode.js +17 -3
- package/dist/sessions/cache.js +5 -0
- package/dist/shared/constants.js +2 -1
- package/dist/shared/types.js +0 -1
- package/dist/tailscale/index.js +80 -0
- package/dist/terminal/websocket.js +1 -1
- package/dist/workspace/manager.js +40 -2
- package/package.json +6 -4
- package/dist/agent/web/assets/index-CGJDysKS.css +0 -1
- package/dist/agent/web/assets/index-CwCl9DVw.js +0 -104
package/dist/agent/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
2
|
import { RPCHandler } from '@orpc/server/node';
|
|
3
3
|
import { loadAgentConfig, getConfigDir, ensureConfigDir } from '../config/loader';
|
|
4
|
-
import { HOST_WORKSPACE_NAME } from '../shared/types';
|
|
4
|
+
import { HOST_WORKSPACE_NAME } from '../shared/client-types';
|
|
5
5
|
import { DEFAULT_AGENT_PORT } from '../shared/constants';
|
|
6
6
|
import { WorkspaceManager } from '../workspace/manager';
|
|
7
7
|
import { containerRunning, getContainerName } from '../docker';
|
|
@@ -13,17 +13,36 @@ import { createRouter } from './router';
|
|
|
13
13
|
import { serveStatic } from './static';
|
|
14
14
|
import { SessionsCacheManager } from '../sessions/cache';
|
|
15
15
|
import { ModelCacheManager } from '../models/cache';
|
|
16
|
+
import { FileWatcher } from './file-watcher';
|
|
17
|
+
import { getTailscaleStatus, getTailscaleIdentity, startTailscaleServe, stopTailscaleServe, } from '../tailscale';
|
|
16
18
|
import pkg from '../../package.json';
|
|
17
19
|
const startTime = Date.now();
|
|
18
20
|
function sendJson(res, status, data) {
|
|
19
21
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
20
22
|
res.end(JSON.stringify(data));
|
|
21
23
|
}
|
|
22
|
-
function createAgentServer(configDir, config) {
|
|
24
|
+
function createAgentServer(configDir, config, tailscale) {
|
|
23
25
|
let currentConfig = config;
|
|
24
26
|
const workspaces = new WorkspaceManager(configDir, currentConfig);
|
|
25
27
|
const sessionsCache = new SessionsCacheManager(configDir);
|
|
26
28
|
const modelCache = new ModelCacheManager(configDir);
|
|
29
|
+
const syncAllRunning = async () => {
|
|
30
|
+
const allWorkspaces = await workspaces.list();
|
|
31
|
+
const running = allWorkspaces.filter((ws) => ws.status === 'running');
|
|
32
|
+
for (const ws of running) {
|
|
33
|
+
try {
|
|
34
|
+
await workspaces.sync(ws.name);
|
|
35
|
+
console.log(`[sync] Synced workspace: ${ws.name}`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error(`[sync] Failed to sync ${ws.name}:`, err);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const fileWatcher = new FileWatcher({
|
|
43
|
+
config: currentConfig,
|
|
44
|
+
syncCallback: syncAllRunning,
|
|
45
|
+
});
|
|
27
46
|
const isWorkspaceRunning = async (name) => {
|
|
28
47
|
if (name === HOST_WORKSPACE_NAME) {
|
|
29
48
|
return currentConfig.allowHostAccess === true;
|
|
@@ -45,6 +64,11 @@ function createAgentServer(configDir, config) {
|
|
|
45
64
|
isHostAccessAllowed: () => currentConfig.allowHostAccess === true,
|
|
46
65
|
getConfig: () => currentConfig,
|
|
47
66
|
});
|
|
67
|
+
const triggerAutoSync = () => {
|
|
68
|
+
syncAllRunning().catch((err) => {
|
|
69
|
+
console.error('[sync] Auto-sync failed:', err);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
48
72
|
const router = createRouter({
|
|
49
73
|
workspaces,
|
|
50
74
|
config: {
|
|
@@ -52,6 +76,7 @@ function createAgentServer(configDir, config) {
|
|
|
52
76
|
set: (newConfig) => {
|
|
53
77
|
currentConfig = newConfig;
|
|
54
78
|
workspaces.updateConfig(newConfig);
|
|
79
|
+
fileWatcher.updateConfig(newConfig);
|
|
55
80
|
},
|
|
56
81
|
},
|
|
57
82
|
configDir,
|
|
@@ -60,12 +85,15 @@ function createAgentServer(configDir, config) {
|
|
|
60
85
|
terminalServer,
|
|
61
86
|
sessionsCache,
|
|
62
87
|
modelCache,
|
|
88
|
+
tailscale,
|
|
89
|
+
triggerAutoSync,
|
|
63
90
|
});
|
|
64
91
|
const rpcHandler = new RPCHandler(router);
|
|
65
92
|
const server = createServer(async (req, res) => {
|
|
66
93
|
const url = new URL(req.url || '/', 'http://localhost');
|
|
67
94
|
const method = req.method;
|
|
68
95
|
const pathname = url.pathname;
|
|
96
|
+
const identity = getTailscaleIdentity(req);
|
|
69
97
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
70
98
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
71
99
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
@@ -76,7 +104,11 @@ function createAgentServer(configDir, config) {
|
|
|
76
104
|
}
|
|
77
105
|
try {
|
|
78
106
|
if (pathname === '/health' && method === 'GET') {
|
|
79
|
-
|
|
107
|
+
const response = { status: 'ok', version: pkg.version };
|
|
108
|
+
if (identity) {
|
|
109
|
+
response.user = identity.email;
|
|
110
|
+
}
|
|
111
|
+
sendJson(res, 200, response);
|
|
80
112
|
return;
|
|
81
113
|
}
|
|
82
114
|
if (pathname.startsWith('/rpc')) {
|
|
@@ -118,7 +150,7 @@ function createAgentServer(configDir, config) {
|
|
|
118
150
|
socket.destroy();
|
|
119
151
|
}
|
|
120
152
|
});
|
|
121
|
-
return { server, terminalServer, chatServer, opencodeServer };
|
|
153
|
+
return { server, terminalServer, chatServer, opencodeServer, fileWatcher };
|
|
122
154
|
}
|
|
123
155
|
async function getProcessUsingPort(port) {
|
|
124
156
|
try {
|
|
@@ -151,7 +183,38 @@ export async function startAgent(options = {}) {
|
|
|
151
183
|
const port = options.port || parseInt(process.env.PERRY_PORT || '', 10) || config.port || DEFAULT_AGENT_PORT;
|
|
152
184
|
console.log(`[agent] Config directory: ${configDir}`);
|
|
153
185
|
console.log(`[agent] Starting on port ${port}...`);
|
|
154
|
-
const
|
|
186
|
+
const tailscale = await getTailscaleStatus();
|
|
187
|
+
let tailscaleServeActive = false;
|
|
188
|
+
if (tailscale.running && tailscale.dnsName) {
|
|
189
|
+
console.log(`[agent] Tailscale detected: ${tailscale.dnsName}`);
|
|
190
|
+
if (!tailscale.httpsEnabled) {
|
|
191
|
+
console.log(`[agent] Tailscale HTTPS not enabled in tailnet, skipping Serve`);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const result = await startTailscaleServe(port);
|
|
195
|
+
if (result.success) {
|
|
196
|
+
tailscaleServeActive = true;
|
|
197
|
+
console.log(`[agent] Tailscale Serve enabled`);
|
|
198
|
+
}
|
|
199
|
+
else if (result.error === 'permission_denied') {
|
|
200
|
+
console.log(`[agent] Tailscale Serve requires operator permissions`);
|
|
201
|
+
console.log(`[agent] To enable: ${result.message}`);
|
|
202
|
+
console.log(`[agent] Continuing without HTTPS...`);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
console.log(`[agent] Tailscale Serve failed: ${result.message || 'unknown error'}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const tailscaleInfo = tailscale.running && tailscale.dnsName
|
|
210
|
+
? {
|
|
211
|
+
running: true,
|
|
212
|
+
dnsName: tailscale.dnsName,
|
|
213
|
+
serveActive: tailscaleServeActive,
|
|
214
|
+
httpsUrl: tailscaleServeActive ? `https://${tailscale.dnsName}` : undefined,
|
|
215
|
+
}
|
|
216
|
+
: undefined;
|
|
217
|
+
const { server, terminalServer, chatServer, opencodeServer, fileWatcher } = createAgentServer(configDir, config, tailscaleInfo);
|
|
155
218
|
server.on('error', async (err) => {
|
|
156
219
|
if (err.code === 'EADDRINUSE') {
|
|
157
220
|
console.error(`[agent] Error: Port ${port} is already in use.`);
|
|
@@ -169,14 +232,26 @@ export async function startAgent(options = {}) {
|
|
|
169
232
|
});
|
|
170
233
|
server.listen(port, '::', () => {
|
|
171
234
|
console.log(`[agent] Agent running at http://localhost:${port}`);
|
|
235
|
+
if (tailscale.running && tailscale.dnsName) {
|
|
236
|
+
const shortName = tailscale.dnsName.split('.')[0];
|
|
237
|
+
console.log(`[agent] Tailnet: http://${shortName}:${port}`);
|
|
238
|
+
if (tailscaleServeActive) {
|
|
239
|
+
console.log(`[agent] Tailnet HTTPS: https://${tailscale.dnsName}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
172
242
|
console.log(`[agent] oRPC endpoint: http://localhost:${port}/rpc`);
|
|
173
243
|
console.log(`[agent] WebSocket terminal: ws://localhost:${port}/rpc/terminal/:name`);
|
|
174
244
|
console.log(`[agent] WebSocket chat (Claude): ws://localhost:${port}/rpc/chat/:name`);
|
|
175
245
|
console.log(`[agent] WebSocket chat (OpenCode): ws://localhost:${port}/rpc/opencode/:name`);
|
|
176
246
|
startEagerImagePull();
|
|
177
247
|
});
|
|
178
|
-
const shutdown = () => {
|
|
248
|
+
const shutdown = async () => {
|
|
179
249
|
console.log('[agent] Shutting down...');
|
|
250
|
+
fileWatcher.stop();
|
|
251
|
+
if (tailscaleServeActive) {
|
|
252
|
+
console.log('[agent] Stopping Tailscale Serve...');
|
|
253
|
+
await stopTailscaleServe();
|
|
254
|
+
}
|
|
180
255
|
chatServer.close();
|
|
181
256
|
opencodeServer.close();
|
|
182
257
|
terminalServer.close();
|