agentp 0.7.0 → 0.9.0

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/lib/opencode.js CHANGED
@@ -11,7 +11,7 @@ function getAuthHeaders() {
11
11
  return { 'Authorization': `Basic ${encoded}` };
12
12
  }
13
13
 
14
- function makeRequest(options, data) {
14
+ function makeRequest(options, data, cancelRef) {
15
15
  return new Promise((resolve, reject) => {
16
16
  const req = http.request(options, (res) => {
17
17
  let body = '';
@@ -25,6 +25,7 @@ function makeRequest(options, data) {
25
25
  });
26
26
  });
27
27
  req.on('error', reject);
28
+ if (cancelRef) cancelRef.current = req;
28
29
  if (data) req.write(data);
29
30
  req.end();
30
31
  });
@@ -181,15 +182,16 @@ async function listSessions(server, directory) {
181
182
  return JSON.parse(body);
182
183
  }
183
184
 
184
- // Send a message directly to a specific session via the session API.
185
+ // Send a message directly to a specific session via the synchronous session API.
185
186
  // Returns the collected text from all text parts in the response.
186
187
  // Optionally specify an agent to handle the message.
187
- async function sendToSession(server, sessionId, text, agent) {
188
+ // Optional cancelRef allows aborting the HTTP request (for /cancel support).
189
+ async function sendToSession(server, sessionId, text, agent, cancelRef) {
188
190
  const url = `${server}/session/${encodeURIComponent(sessionId)}/message`;
189
191
  const bodyObj = { parts: [{ type: 'text', text }] };
190
192
  if (agent) bodyObj.agent = agent;
191
193
  const body = JSON.stringify(bodyObj);
192
- const { status, body: responseBody } = await makeRequest(buildJsonRequest(url, 'POST', body), body);
194
+ const { status, body: responseBody } = await makeRequest(buildJsonRequest(url, 'POST', body), body, cancelRef);
193
195
  if (status !== 200) throw new Error(`Failed to send to session: ${status}`);
194
196
  const result = JSON.parse(responseBody);
195
197
  const texts = (result.parts || [])
@@ -209,9 +211,12 @@ async function createSession(server, title) {
209
211
  }
210
212
 
211
213
  // Update a session's properties (e.g. title).
212
- async function updateSession(server, sessionId, title) {
214
+ async function updateSession(server, sessionId, title, agent) {
215
+ const bodyObj = {};
216
+ if (title != null) bodyObj.title = title;
217
+ if (agent != null) bodyObj.agent = agent;
213
218
  const url = `${server}/session/${encodeURIComponent(sessionId)}`;
214
- const body = JSON.stringify({ title });
219
+ const body = JSON.stringify(bodyObj);
215
220
  const { status, body: responseBody } = await makeRequest(buildJsonRequest(url, 'PATCH', body), body);
216
221
  if (status !== 200) throw new Error(`Failed to update session: ${status}`);
217
222
  return JSON.parse(responseBody);
@@ -222,13 +227,16 @@ async function updateSession(server, sessionId, title) {
222
227
  async function selectSession(server, sessionId) {
223
228
  const encoded = encodeURIComponent(sessionId);
224
229
  const attempts = [
225
- { url: `${server}/session/${encoded}/select`, body: '{}' },
226
230
  { url: `${server}/tui/select-session`, body: JSON.stringify({ sessionID: sessionId }) },
231
+ { url: `${server}/session/${encoded}/select`, body: '{}' },
227
232
  ];
228
233
  for (const { url, body } of attempts) {
229
234
  try {
230
- await makeRequest(buildJsonRequest(url, 'POST', body), body);
231
- return;
235
+ const { status } = await makeRequest(buildJsonRequest(url, 'POST', body), body);
236
+ // /session/:id/select returns 200 with HTML even for bad IDs (TUI page),
237
+ // but doesn't actually navigate. Only consider it a success if the
238
+ // response looks like JSON (i.e. not HTML).
239
+ if (status === 200) return;
232
240
  } catch {
233
241
  // try next approach
234
242
  }
@@ -273,7 +281,7 @@ async function respondToPermission(server, sessionId, permissionId, response, re
273
281
 
274
282
  // Listen for session events via SSE, collecting assistant text parts and detecting
275
283
  // permission requests. Resolves with the full collected text when the session goes idle.
276
- // callbacks: { onText(chunk), onPermission(permission) }
284
+ // callbacks: { onText(chunk), onPermission(permission), onThinking(chunk), onConnected() }
277
285
  // cancelRef — allows aborting the SSE stream via req.destroy()
278
286
  function listenForSessionEvents(server, sessionId, callbacks, cancelRef) {
279
287
  process.stderr.write(` [SSE] connecting to ${server}/event for session ${sessionId}\n`);
@@ -294,9 +302,22 @@ function listenForSessionEvents(server, sessionId, callbacks, cancelRef) {
294
302
  return;
295
303
  }
296
304
  process.stderr.write(` [SSE] connected for session ${sessionId}\n`);
305
+ if (callbacks.onConnected) callbacks.onConnected();
297
306
  let buffer = '';
298
307
  const userMessageIDs = new Set();
299
308
  let collected = '';
309
+ let resolved = false;
310
+
311
+ // Safety timeout: resolve with whatever we have after 90s
312
+ const safetyTimer = setTimeout(() => {
313
+ if (resolved) return;
314
+ resolved = true;
315
+ process.stderr.write(` [SSE] safety timeout (90s) for session ${sessionId}, collected ${collected.length} chars\n`);
316
+ cleanup();
317
+ req.destroy();
318
+ res.destroy();
319
+ resolve(collected);
320
+ }, 90000);
300
321
 
301
322
  res.on('data', (chunk) => {
302
323
  buffer += chunk.toString();
@@ -322,8 +343,8 @@ function listenForSessionEvents(server, sessionId, callbacks, cancelRef) {
322
343
  process.stderr.write(` [SSE] event type=${event.type} sessionID=${props.sessionID} waitSession=${sessionId}\n`);
323
344
  }
324
345
 
325
- // Filter by sessionID when the event carries one
326
- if (props.sessionID && props.sessionID !== sessionId) continue;
346
+ // Filter by sessionID when the event carries one (string-compare to handle type mismatches)
347
+ if (props.sessionID && String(props.sessionID) !== String(sessionId)) continue;
327
348
 
328
349
  if (event.type === 'message.updated' && props.info?.role === 'user') {
329
350
  userMessageIDs.add(props.info.id);
@@ -352,6 +373,9 @@ function listenForSessionEvents(server, sessionId, callbacks, cancelRef) {
352
373
 
353
374
  // Completion signals
354
375
  if (event.type === 'session.idle') {
376
+ clearTimeout(safetyTimer);
377
+ if (resolved) return;
378
+ resolved = true;
355
379
  cleanup();
356
380
  req.destroy();
357
381
  res.destroy();
@@ -359,6 +383,9 @@ function listenForSessionEvents(server, sessionId, callbacks, cancelRef) {
359
383
  return;
360
384
  }
361
385
  if (event.type === 'session.status' && props.status?.type === 'idle') {
386
+ clearTimeout(safetyTimer);
387
+ if (resolved) return;
388
+ resolved = true;
362
389
  cleanup();
363
390
  req.destroy();
364
391
  res.destroy();
@@ -376,6 +403,10 @@ function listenForSessionEvents(server, sessionId, callbacks, cancelRef) {
376
403
  if (callbacks.onText) callbacks.onText(part.text);
377
404
  collected += part.text;
378
405
  }
406
+
407
+ if ((part.type === 'reasoning' || part.type === 'thinking') && part.text != null && part.text !== '') {
408
+ if (callbacks.onThinking) callbacks.onThinking(part.text);
409
+ }
379
410
  } catch (e) {
380
411
  // Malformed SSE event — skip
381
412
  }
@@ -383,9 +414,21 @@ function listenForSessionEvents(server, sessionId, callbacks, cancelRef) {
383
414
  });
384
415
 
385
416
  res.on('end', () => {
417
+ clearTimeout(safetyTimer);
418
+ if (resolved) return;
419
+ resolved = true;
386
420
  cleanup();
387
421
  resolve(collected);
388
422
  });
423
+
424
+ res.on('error', (err) => {
425
+ clearTimeout(safetyTimer);
426
+ if (resolved) return;
427
+ resolved = true;
428
+ cleanup();
429
+ process.stderr.write(` [SSE] response stream error for session ${sessionId}: ${err.message}\n`);
430
+ resolve(collected); // resolve with what we have
431
+ });
389
432
  });
390
433
 
391
434
  const cleanup = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentp",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Pipe prompts into a running OpenCode TUI session and stream the final answer",
5
5
  "bin": {
6
6
  "agentp": "bin/agentp",