pi-web 0.13.1 → 0.13.2
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/build/server/rpc.js +2 -3
- package/build/server/server.js +57 -23
- package/build/server/sessions.js +18 -26
- package/dist/assets/index-BEnbXRXM.js +46 -0
- package/dist/index.html +1 -1
- package/package.json +2 -1
- package/dist/assets/index-CmLShYTq.js +0 -46
package/build/server/rpc.js
CHANGED
|
@@ -6,9 +6,8 @@ export class RpcSession {
|
|
|
6
6
|
opts;
|
|
7
7
|
constructor(opts) {
|
|
8
8
|
this.opts = opts;
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const args = [...parts.slice(1), '--mode', 'rpc'];
|
|
9
|
+
const cmd = opts.piCmd.command;
|
|
10
|
+
const args = [...opts.piCmd.args, '--mode', 'rpc'];
|
|
12
11
|
this.proc = spawn(cmd, args, {
|
|
13
12
|
cwd: opts.cwd,
|
|
14
13
|
stdio: ['pipe', 'pipe', 'pipe'],
|
package/build/server/server.js
CHANGED
|
@@ -42,8 +42,8 @@ function parseAgent(value) {
|
|
|
42
42
|
}
|
|
43
43
|
function getAgentCommand(agent) {
|
|
44
44
|
return agent === 'omp'
|
|
45
|
-
? 'npx -y @oh-my-pi/pi-coding-agent@latest'
|
|
46
|
-
: 'npx -y @mariozechner/pi-coding-agent@latest';
|
|
45
|
+
? { command: 'npx', args: ['-y', '@oh-my-pi/pi-coding-agent@latest'] }
|
|
46
|
+
: { command: 'npx', args: ['-y', '@mariozechner/pi-coding-agent@latest'] };
|
|
47
47
|
}
|
|
48
48
|
const AGENT = parseAgent(getArg('agent'));
|
|
49
49
|
const PORT = parseInt(getArg('port') || '8192', 10);
|
|
@@ -59,6 +59,7 @@ const distDir = distDirCandidates.find((candidate) => existsSync(join(candidate,
|
|
|
59
59
|
const htmlPath = join(distDir, 'index.html');
|
|
60
60
|
const htmlCache = isDev || !existsSync(htmlPath) ? null : readFileSync(htmlPath, 'utf-8');
|
|
61
61
|
const HOME_DIR = resolve(homedir() || '/');
|
|
62
|
+
const SESSION_ROOT = resolve(join(HOME_DIR, AGENT === 'omp' ? '.omp' : '.pi', 'agent', 'sessions'));
|
|
62
63
|
function isWithinRoot(path, root) {
|
|
63
64
|
const rel = relative(root, path);
|
|
64
65
|
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
@@ -103,6 +104,22 @@ function serveFile(filePath, res) {
|
|
|
103
104
|
res.writeHead(200, { 'Content-Type': contentTypes[ext] || 'application/octet-stream' });
|
|
104
105
|
createReadStream(filePath).pipe(res);
|
|
105
106
|
}
|
|
107
|
+
function logServerError(context, error) {
|
|
108
|
+
console.error(`[pi-web] ${context}`, error);
|
|
109
|
+
}
|
|
110
|
+
function respondJson(res, statusCode, payload) {
|
|
111
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
112
|
+
res.end(JSON.stringify(payload));
|
|
113
|
+
}
|
|
114
|
+
function isValidSessionFilename(filename) {
|
|
115
|
+
return basename(filename) === filename && filename.endsWith('.jsonl');
|
|
116
|
+
}
|
|
117
|
+
function resolveSessionPath(cwd, filename) {
|
|
118
|
+
if (!isValidSessionFilename(filename))
|
|
119
|
+
return null;
|
|
120
|
+
const resolved = resolve(getSessionFilePath(cwd, filename, AGENT));
|
|
121
|
+
return isWithinRoot(resolved, SESSION_ROOT) ? resolved : null;
|
|
122
|
+
}
|
|
106
123
|
const server = createServer((req, res) => {
|
|
107
124
|
if (req.url === '/' || req.url === '/index.html') {
|
|
108
125
|
if (!existsSync(htmlPath)) {
|
|
@@ -123,12 +140,11 @@ const server = createServer((req, res) => {
|
|
|
123
140
|
...session,
|
|
124
141
|
...getSessionRuntimeStatus(session.cwd, session.file),
|
|
125
142
|
}));
|
|
126
|
-
res
|
|
127
|
-
res.end(JSON.stringify(sessionsWithRuntime));
|
|
143
|
+
respondJson(res, 200, sessionsWithRuntime);
|
|
128
144
|
})
|
|
129
145
|
.catch((err) => {
|
|
130
|
-
|
|
131
|
-
res
|
|
146
|
+
logServerError('failed to list sessions', err);
|
|
147
|
+
respondJson(res, 500, { error: 'failed to list sessions' });
|
|
132
148
|
});
|
|
133
149
|
return;
|
|
134
150
|
}
|
|
@@ -137,12 +153,11 @@ const server = createServer((req, res) => {
|
|
|
137
153
|
const cwd = url.searchParams.get('cwd');
|
|
138
154
|
listFolders(cwd)
|
|
139
155
|
.then((data) => {
|
|
140
|
-
res
|
|
141
|
-
res.end(JSON.stringify(data));
|
|
156
|
+
respondJson(res, 200, data);
|
|
142
157
|
})
|
|
143
158
|
.catch((err) => {
|
|
144
|
-
|
|
145
|
-
res
|
|
159
|
+
logServerError('failed to list folders', err);
|
|
160
|
+
respondJson(res, 500, { error: 'failed to list folders' });
|
|
146
161
|
});
|
|
147
162
|
return;
|
|
148
163
|
}
|
|
@@ -151,31 +166,36 @@ const server = createServer((req, res) => {
|
|
|
151
166
|
const cwd = url.searchParams.get('cwd');
|
|
152
167
|
const filename = url.searchParams.get('filename');
|
|
153
168
|
if (!cwd || !filename) {
|
|
154
|
-
res
|
|
155
|
-
|
|
169
|
+
respondJson(res, 400, { error: 'cwd and filename parameters required' });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const file = resolveSessionPath(cwd, filename);
|
|
173
|
+
if (!file) {
|
|
174
|
+
respondJson(res, 400, { error: 'invalid session path' });
|
|
156
175
|
return;
|
|
157
176
|
}
|
|
158
|
-
const file = getSessionFilePath(cwd, filename, AGENT);
|
|
159
177
|
if (req.method === 'DELETE') {
|
|
160
178
|
try {
|
|
179
|
+
const managed = findManagedSessionByFile(cwd, filename);
|
|
180
|
+
if (managed) {
|
|
181
|
+
closeManagedSession(managed);
|
|
182
|
+
}
|
|
161
183
|
unlinkSync(file);
|
|
162
|
-
res
|
|
163
|
-
res.end(JSON.stringify({ ok: true }));
|
|
184
|
+
respondJson(res, 200, { ok: true });
|
|
164
185
|
}
|
|
165
186
|
catch (err) {
|
|
166
|
-
|
|
167
|
-
res
|
|
187
|
+
logServerError(`failed to delete session ${file}`, err);
|
|
188
|
+
respondJson(res, 500, { error: 'failed to delete session' });
|
|
168
189
|
}
|
|
169
190
|
return;
|
|
170
191
|
}
|
|
171
192
|
readSessionMessages(file)
|
|
172
193
|
.then((messages) => {
|
|
173
|
-
res
|
|
174
|
-
res.end(JSON.stringify(messages));
|
|
194
|
+
respondJson(res, 200, messages);
|
|
175
195
|
})
|
|
176
196
|
.catch((err) => {
|
|
177
|
-
|
|
178
|
-
res
|
|
197
|
+
logServerError(`failed to read session ${file}`, err);
|
|
198
|
+
respondJson(res, 500, { error: 'failed to read session' });
|
|
179
199
|
});
|
|
180
200
|
return;
|
|
181
201
|
}
|
|
@@ -184,8 +204,8 @@ const server = createServer((req, res) => {
|
|
|
184
204
|
const safePath = normalize(url.pathname)
|
|
185
205
|
.replace(/^(\.\.[/\\])+/, '')
|
|
186
206
|
.replace(/^[/\\]+/, '');
|
|
187
|
-
const filePath = join(distDir, safePath);
|
|
188
|
-
if (filePath
|
|
207
|
+
const filePath = resolve(join(distDir, safePath));
|
|
208
|
+
if (isWithinRoot(filePath, distDir) && existsSync(filePath) && statSync(filePath).isFile()) {
|
|
189
209
|
serveFile(filePath, res);
|
|
190
210
|
return;
|
|
191
211
|
}
|
|
@@ -266,6 +286,19 @@ function closeManagedSession(managed) {
|
|
|
266
286
|
unregisterManagedSession(managed);
|
|
267
287
|
managed.rpc.kill();
|
|
268
288
|
}
|
|
289
|
+
function findManagedSessionByFile(cwd, sessionFile) {
|
|
290
|
+
const key = buildSessionKey(resolve(cwd), basename(sessionFile));
|
|
291
|
+
const direct = rpcSessions.get(key);
|
|
292
|
+
if (direct && !direct.isClosing)
|
|
293
|
+
return direct;
|
|
294
|
+
for (const managed of rpcSessions.values()) {
|
|
295
|
+
if (managed.isClosing)
|
|
296
|
+
continue;
|
|
297
|
+
if (managed.cwd === resolve(cwd) && managed.keys.has(key))
|
|
298
|
+
return managed;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
269
302
|
function cleanupIfIdle(managed) {
|
|
270
303
|
if (managed.isClosing)
|
|
271
304
|
return;
|
|
@@ -363,6 +396,7 @@ function createManagedSession(cwd, sessionFile, initialKey) {
|
|
|
363
396
|
onError: (error) => {
|
|
364
397
|
if (!managed)
|
|
365
398
|
return;
|
|
399
|
+
logServerError(`rpc session error (${managed.cwd}${managed.sessionFile ? `/${managed.sessionFile}` : ''})`, error);
|
|
366
400
|
broadcast(managed, { type: 'error', message: error });
|
|
367
401
|
},
|
|
368
402
|
onExit: (code) => {
|
package/build/server/sessions.js
CHANGED
|
@@ -70,7 +70,7 @@ export async function readSessionMessages(filePath) {
|
|
|
70
70
|
try {
|
|
71
71
|
for await (const line of rl) {
|
|
72
72
|
const trimmed = line.trim();
|
|
73
|
-
if (!trimmed
|
|
73
|
+
if (!trimmed)
|
|
74
74
|
continue;
|
|
75
75
|
try {
|
|
76
76
|
const entry = JSON.parse(trimmed);
|
|
@@ -140,36 +140,28 @@ async function readSessionHeader(filePath) {
|
|
|
140
140
|
const trimmed = line.trim();
|
|
141
141
|
if (!trimmed)
|
|
142
142
|
continue;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(trimmed);
|
|
145
|
+
if (!header && parsed.type === 'session') {
|
|
146
|
+
header = parsed;
|
|
147
|
+
continue;
|
|
150
148
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (trimmed.startsWith('{"type":"message"')) {
|
|
149
|
+
if (parsed.type !== 'message')
|
|
150
|
+
continue;
|
|
154
151
|
messageCount++;
|
|
155
|
-
if (!firstPrompt &&
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const text = content.find((c) => c.type === 'text');
|
|
165
|
-
if (text?.text)
|
|
166
|
-
firstPrompt = text.text.slice(0, 120);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
152
|
+
if (!firstPrompt && parsed.message?.role === 'user') {
|
|
153
|
+
const content = parsed.message.content;
|
|
154
|
+
if (typeof content === 'string') {
|
|
155
|
+
firstPrompt = content.slice(0, 120);
|
|
156
|
+
}
|
|
157
|
+
else if (Array.isArray(content)) {
|
|
158
|
+
const text = content.find((c) => c.type === 'text');
|
|
159
|
+
if (text?.text)
|
|
160
|
+
firstPrompt = text.text.slice(0, 120);
|
|
169
161
|
}
|
|
170
|
-
catch { }
|
|
171
162
|
}
|
|
172
163
|
}
|
|
164
|
+
catch { }
|
|
173
165
|
}
|
|
174
166
|
}
|
|
175
167
|
finally {
|