@gricha/perry 0.1.1 → 0.1.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/dist/client/ws-shell.js +5 -1
- package/dist/index.js +5 -15
- package/dist/sessions/agents/opencode.js +48 -146
- package/package.json +1 -1
package/dist/client/ws-shell.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
|
+
import { DEFAULT_AGENT_PORT } from '../shared/constants';
|
|
3
4
|
export function isLocalWorker(worker) {
|
|
4
5
|
const host = worker
|
|
5
6
|
.replace(/^https?:\/\//, '')
|
|
@@ -125,6 +126,9 @@ export function getTerminalWSUrl(worker, workspaceName) {
|
|
|
125
126
|
base = `http://${base}`;
|
|
126
127
|
}
|
|
127
128
|
const wsProtocol = base.startsWith('https://') ? 'wss://' : 'ws://';
|
|
128
|
-
|
|
129
|
+
let host = base.replace(/^https?:\/\//, '');
|
|
130
|
+
if (!host.includes(':')) {
|
|
131
|
+
host = `${host}:${DEFAULT_AGENT_PORT}`;
|
|
132
|
+
}
|
|
129
133
|
return `${wsProtocol}${host}/rpc/terminal/${encodeURIComponent(workspaceName)}`;
|
|
130
134
|
}
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,10 @@ async function checkLocalAgent() {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
async function getClient() {
|
|
86
|
+
const worker = await getWorkerWithFallback();
|
|
87
|
+
return createApiClient(worker);
|
|
88
|
+
}
|
|
89
|
+
async function getWorkerWithFallback() {
|
|
86
90
|
let worker = await getWorker();
|
|
87
91
|
if (!worker) {
|
|
88
92
|
const localRunning = await checkLocalAgent();
|
|
@@ -94,7 +98,7 @@ async function getClient() {
|
|
|
94
98
|
process.exit(1);
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
|
-
return
|
|
101
|
+
return worker;
|
|
98
102
|
}
|
|
99
103
|
program
|
|
100
104
|
.command('list')
|
|
@@ -282,20 +286,6 @@ program
|
|
|
282
286
|
handleError(err);
|
|
283
287
|
}
|
|
284
288
|
});
|
|
285
|
-
async function getWorkerWithFallback() {
|
|
286
|
-
let worker = await getWorker();
|
|
287
|
-
if (!worker) {
|
|
288
|
-
const localRunning = await checkLocalAgent();
|
|
289
|
-
if (localRunning) {
|
|
290
|
-
worker = `localhost:${DEFAULT_AGENT_PORT}`;
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
console.error('No worker configured. Run: perry config worker <hostname>');
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return worker;
|
|
298
|
-
}
|
|
299
289
|
program
|
|
300
290
|
.command('proxy <name> [ports...]')
|
|
301
291
|
.description('Forward ports from workspace to local machine')
|
|
@@ -1,168 +1,70 @@
|
|
|
1
|
-
import { extractContent } from './utils';
|
|
2
1
|
export const opencodeProvider = {
|
|
3
2
|
async discoverSessions(containerName, exec) {
|
|
4
|
-
const result = await exec(containerName, [
|
|
5
|
-
'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
agentType: 'opencode',
|
|
24
|
-
projectPath: data.directory || '',
|
|
25
|
-
mtime,
|
|
26
|
-
name: data.title || undefined,
|
|
27
|
-
filePath: file,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// Skip on parse error
|
|
33
|
-
}
|
|
34
|
-
}
|
|
3
|
+
const result = await exec(containerName, ['perry-session-reader', 'list'], {
|
|
4
|
+
user: 'workspace',
|
|
5
|
+
});
|
|
6
|
+
if (result.exitCode !== 0) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const sessionData = JSON.parse(result.stdout);
|
|
11
|
+
return sessionData.map((data) => ({
|
|
12
|
+
id: data.id,
|
|
13
|
+
agentType: 'opencode',
|
|
14
|
+
projectPath: data.directory || '',
|
|
15
|
+
mtime: Math.floor(data.mtime / 1000),
|
|
16
|
+
name: data.title || undefined,
|
|
17
|
+
filePath: data.file,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return [];
|
|
35
22
|
}
|
|
36
|
-
return sessions;
|
|
37
23
|
},
|
|
38
24
|
async getSessionDetails(containerName, rawSession, exec) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
continue;
|
|
50
|
-
try {
|
|
51
|
-
const msg = JSON.parse(msgResult.stdout);
|
|
52
|
-
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
53
|
-
const content = extractContent(msg.content);
|
|
54
|
-
messages.push({ type: msg.role, content: content || undefined });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
25
|
+
const result = await exec(containerName, ['perry-session-reader', 'messages', rawSession.id], {
|
|
26
|
+
user: 'workspace',
|
|
27
|
+
});
|
|
28
|
+
if (result.exitCode !== 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const data = JSON.parse(result.stdout);
|
|
33
|
+
if (data.messages.length === 0) {
|
|
34
|
+
return null;
|
|
60
35
|
}
|
|
36
|
+
const messages = data.messages.filter((m) => m.type === 'user' || m.type === 'assistant');
|
|
37
|
+
const firstPrompt = messages.find((msg) => msg.type === 'user' && msg.content && msg.content.trim().length > 0)?.content;
|
|
38
|
+
return {
|
|
39
|
+
id: rawSession.id,
|
|
40
|
+
name: rawSession.name || null,
|
|
41
|
+
agentType: rawSession.agentType,
|
|
42
|
+
projectPath: rawSession.projectPath,
|
|
43
|
+
messageCount: messages.length,
|
|
44
|
+
lastActivity: new Date(rawSession.mtime * 1000).toISOString(),
|
|
45
|
+
firstPrompt: firstPrompt ? firstPrompt.slice(0, 200) : null,
|
|
46
|
+
};
|
|
61
47
|
}
|
|
62
|
-
|
|
63
|
-
if (messages.length === 0) {
|
|
48
|
+
catch {
|
|
64
49
|
return null;
|
|
65
50
|
}
|
|
66
|
-
return {
|
|
67
|
-
id: rawSession.id,
|
|
68
|
-
name: rawSession.name || null,
|
|
69
|
-
agentType: rawSession.agentType,
|
|
70
|
-
projectPath: rawSession.projectPath,
|
|
71
|
-
messageCount: messages.length,
|
|
72
|
-
lastActivity: new Date(rawSession.mtime * 1000).toISOString(),
|
|
73
|
-
firstPrompt: firstPrompt ? firstPrompt.slice(0, 200) : null,
|
|
74
|
-
};
|
|
75
51
|
},
|
|
76
52
|
async getSessionMessages(containerName, sessionId, exec) {
|
|
77
|
-
const
|
|
78
|
-
'bash',
|
|
79
|
-
'-c',
|
|
80
|
-
`find /home/workspace/.local/share/opencode/storage/session -name "${sessionId}.json" -type f 2>/dev/null | head -1`,
|
|
81
|
-
], { user: 'workspace' });
|
|
82
|
-
if (findResult.exitCode !== 0 || !findResult.stdout.trim()) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
const filePath = findResult.stdout.trim();
|
|
86
|
-
const catResult = await exec(containerName, ['cat', filePath], {
|
|
53
|
+
const result = await exec(containerName, ['perry-session-reader', 'messages', sessionId], {
|
|
87
54
|
user: 'workspace',
|
|
88
55
|
});
|
|
89
|
-
if (
|
|
56
|
+
if (result.exitCode !== 0) {
|
|
90
57
|
return null;
|
|
91
58
|
}
|
|
92
|
-
let internalId;
|
|
93
59
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
60
|
+
const data = JSON.parse(result.stdout);
|
|
61
|
+
return {
|
|
62
|
+
id: sessionId,
|
|
63
|
+
messages: data.messages,
|
|
64
|
+
};
|
|
96
65
|
}
|
|
97
66
|
catch {
|
|
98
67
|
return null;
|
|
99
68
|
}
|
|
100
|
-
const msgDir = `/home/workspace/.local/share/opencode/storage/message/${internalId}`;
|
|
101
|
-
const partDir = `/home/workspace/.local/share/opencode/storage/part`;
|
|
102
|
-
const listMsgsResult = await exec(containerName, ['bash', '-c', `ls -1 "${msgDir}"/msg_*.json 2>/dev/null | sort`], { user: 'workspace' });
|
|
103
|
-
if (listMsgsResult.exitCode !== 0 || !listMsgsResult.stdout.trim()) {
|
|
104
|
-
return { id: sessionId, messages: [] };
|
|
105
|
-
}
|
|
106
|
-
const messages = [];
|
|
107
|
-
const msgFiles = listMsgsResult.stdout.trim().split('\n').filter(Boolean);
|
|
108
|
-
for (const msgFile of msgFiles) {
|
|
109
|
-
const msgResult = await exec(containerName, ['cat', msgFile], {
|
|
110
|
-
user: 'workspace',
|
|
111
|
-
});
|
|
112
|
-
if (msgResult.exitCode !== 0)
|
|
113
|
-
continue;
|
|
114
|
-
try {
|
|
115
|
-
const msg = JSON.parse(msgResult.stdout);
|
|
116
|
-
if (!msg.id || (msg.role !== 'user' && msg.role !== 'assistant'))
|
|
117
|
-
continue;
|
|
118
|
-
const timestamp = msg.time?.created ? new Date(msg.time.created).toISOString() : undefined;
|
|
119
|
-
const listPartsResult = await exec(containerName, ['bash', '-c', `ls -1 "${partDir}/${msg.id}"/prt_*.json 2>/dev/null | sort`], { user: 'workspace' });
|
|
120
|
-
if (listPartsResult.exitCode === 0 && listPartsResult.stdout.trim()) {
|
|
121
|
-
const partFiles = listPartsResult.stdout.trim().split('\n').filter(Boolean);
|
|
122
|
-
for (const partFile of partFiles) {
|
|
123
|
-
const partResult = await exec(containerName, ['cat', partFile], {
|
|
124
|
-
user: 'workspace',
|
|
125
|
-
});
|
|
126
|
-
if (partResult.exitCode !== 0)
|
|
127
|
-
continue;
|
|
128
|
-
try {
|
|
129
|
-
const part = JSON.parse(partResult.stdout);
|
|
130
|
-
if (part.type === 'text' && part.text) {
|
|
131
|
-
messages.push({
|
|
132
|
-
type: msg.role,
|
|
133
|
-
content: part.text,
|
|
134
|
-
timestamp,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
else if (part.type === 'tool' && part.tool) {
|
|
138
|
-
messages.push({
|
|
139
|
-
type: 'tool_use',
|
|
140
|
-
content: undefined,
|
|
141
|
-
toolName: part.state?.title || part.tool,
|
|
142
|
-
toolId: part.callID || part.id,
|
|
143
|
-
toolInput: JSON.stringify(part.state?.input, null, 2),
|
|
144
|
-
timestamp,
|
|
145
|
-
});
|
|
146
|
-
if (part.state?.output) {
|
|
147
|
-
messages.push({
|
|
148
|
-
type: 'tool_result',
|
|
149
|
-
content: part.state.output,
|
|
150
|
-
toolId: part.callID || part.id,
|
|
151
|
-
timestamp,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return { id: sessionId, messages };
|
|
167
69
|
},
|
|
168
70
|
};
|