@pstdio/pocketcoder-remote 0.3.2 → 0.3.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/bin.js CHANGED
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { dirname, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- //#region src/launch.ts
6
+ //#region src/session/launch.ts
7
7
  const PI_FLAGS = [
8
8
  "--provider",
9
9
  "pocketcoder-agentapi",
@@ -30,7 +30,8 @@ function piBinPath(resolvePath) {
30
30
  return resolve(packageRoot, bin);
31
31
  }
32
32
  function extensionPath(moduleDir) {
33
- return resolve(moduleDir, moduleDir.endsWith(`${sep}src`) ? "extension.ts" : "extension.js");
33
+ if (moduleDir.endsWith(`${sep}src${sep}session`)) return resolve(moduleDir, "../extension.ts");
34
+ return resolve(moduleDir, "extension.js");
34
35
  }
35
36
  function resolvePiInvocation(options = {}) {
36
37
  const env = options.env ?? process.env;
package/dist/extension.js CHANGED
@@ -2,9 +2,9 @@ import { readFileSync, statSync } from "node:fs";
2
2
  import { basename, extname, isAbsolute, resolve } from "node:path";
3
3
  import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
4
4
  import { ConversationGoneError, PocketCoderClient, PocketCoderError, TERMINAL_WORKSPACE_STATES, WorkspaceTerminalError, WorkspaceTurnResolutionError, isPocketCoderErrorCode, splitAttachmentManifest } from "@pstdio/pocketcoder-sdk";
5
- import { randomUUID } from "node:crypto";
6
5
  import { Text } from "@earendil-works/pi-tui";
7
- //#region src/control-plane.ts
6
+ import { randomUUID } from "node:crypto";
7
+ //#region src/client/control-plane.ts
8
8
  /** @internal The remote UI keeps its historical config vocabulary at its boundary. */
9
9
  var ControlPlaneClient = class extends PocketCoderClient {
10
10
  constructor(config, fetchImpl = fetch) {
@@ -15,7 +15,7 @@ var ControlPlaneClient = class extends PocketCoderClient {
15
15
  }
16
16
  };
17
17
  //#endregion
18
- //#region src/remote-request-error.ts
18
+ //#region src/client/remote-request-error.ts
19
19
  var RemoteRequestError = class extends Error {
20
20
  phase;
21
21
  promptAccepted;
@@ -73,7 +73,7 @@ async function remoteResponseError(response, phase, promptAccepted) {
73
73
  });
74
74
  }
75
75
  //#endregion
76
- //#region src/attachments.ts
76
+ //#region src/attachments/attachments.ts
77
77
  const DIRECT_MODE_ATTACHMENT_ERROR = "file attachments need the PocketCoder workspace API; a direct AgentAPI URL (POCKETCODER_AGENTAPI_URL) cannot accept managed uploads";
78
78
  const IMAGE_EXTENSIONS = {
79
79
  "image/gif": "gif",
@@ -221,7 +221,119 @@ function registerAttachCommand(pi, deps) {
221
221
  });
222
222
  }
223
223
  //#endregion
224
- //#region src/session-target.ts
224
+ //#region src/ui/renderers.ts
225
+ const HISTORY_ENTRY_TYPE = "pocketcoder-conversation";
226
+ const NOTICE_ENTRY_TYPE = "pocketcoder-history-notice";
227
+ function roleHeader(data, theme) {
228
+ const time = data.occurred_at.replace("T", " ").replace(/\.\d+Z?$|Z$/, "");
229
+ return theme.fg("muted", `${data.role} · ${time}`);
230
+ }
231
+ function userBody(content, theme) {
232
+ const { text, attachments } = splitAttachmentManifest(content);
233
+ const body = theme.fg("userMessageText", text);
234
+ if (!attachments) return body;
235
+ return `${body}\n${attachments.map((attachment) => theme.fg("muted", `⌁ ${attachment.name} (${attachment.size_bytes} bytes)`)).join("\n")}`;
236
+ }
237
+ function formatConversationMessage(data, theme) {
238
+ switch (data.kind ?? "text") {
239
+ default: {
240
+ const header = roleHeader(data, theme);
241
+ let body = data.content;
242
+ if (data.role === "user") body = userBody(data.content, theme);
243
+ else if (data.role !== "assistant") body = theme.fg("dim", data.content);
244
+ return `${header}\n${body}`;
245
+ }
246
+ }
247
+ }
248
+ function formatHistoryNotice(data, theme) {
249
+ return theme.fg(data.level === "warning" ? "warning" : "muted", data.text);
250
+ }
251
+ function registerConversationRenderers(pi) {
252
+ pi.registerEntryRenderer(HISTORY_ENTRY_TYPE, (entry, _options, theme) => {
253
+ if (!entry.data) return void 0;
254
+ return new Text(formatConversationMessage(entry.data, theme), 1, 0);
255
+ });
256
+ pi.registerEntryRenderer(NOTICE_ENTRY_TYPE, (entry, _options, theme) => {
257
+ if (!entry.data) return void 0;
258
+ return new Text(formatHistoryNotice(entry.data, theme), 1, 0);
259
+ });
260
+ }
261
+ //#endregion
262
+ //#region src/history/history.ts
263
+ async function collectTranscriptTail(controlPlane, workspaceId, options) {
264
+ const tail = [];
265
+ let total = 0;
266
+ let pages = 0;
267
+ let cursor;
268
+ while (pages < options.maxPages) {
269
+ const page = await controlPlane.conversations.list(workspaceId, {
270
+ cursor,
271
+ limit: options.pageLimit
272
+ });
273
+ pages += 1;
274
+ for (const message of page.items) {
275
+ total += 1;
276
+ tail.push({
277
+ role: message.role,
278
+ content: message.content,
279
+ seq: message.seq,
280
+ occurred_at: message.occurred_at,
281
+ kind: message.metadata?.kind,
282
+ metadata: message.metadata
283
+ });
284
+ if (tail.length > options.maxMessages) tail.shift();
285
+ }
286
+ if (page.nextCursor === null) return {
287
+ tail,
288
+ total,
289
+ pages,
290
+ exhaustedPages: false
291
+ };
292
+ cursor = page.nextCursor;
293
+ }
294
+ return {
295
+ tail,
296
+ total,
297
+ pages,
298
+ exhaustedPages: true
299
+ };
300
+ }
301
+ async function replayHistory(pi, controlPlane, workspaceId, options = {}) {
302
+ let transcript;
303
+ try {
304
+ transcript = await collectTranscriptTail(controlPlane, workspaceId, {
305
+ pageLimit: options.pageLimit ?? 200,
306
+ maxMessages: options.maxMessages ?? 1e3,
307
+ maxPages: options.maxPages ?? 50
308
+ });
309
+ } catch (error) {
310
+ if (error instanceof ConversationGoneError) {
311
+ pi.appendEntry(NOTICE_ENTRY_TYPE, {
312
+ text: error.code === "conversation.deleted" ? "conversation history was deleted" : "conversation history has expired",
313
+ level: "warning"
314
+ });
315
+ return {
316
+ replayed: 0,
317
+ total: 0,
318
+ truncated: false,
319
+ gone: true
320
+ };
321
+ }
322
+ throw error;
323
+ }
324
+ const { tail, total, pages, exhaustedPages } = transcript;
325
+ const truncated = total > tail.length || exhaustedPages;
326
+ if (truncated) pi.appendEntry(NOTICE_ENTRY_TYPE, { text: exhaustedPages ? `history partially replayed (stopped after ${pages} pages)` : `showing last ${tail.length} of ${total} messages` });
327
+ for (const message of tail) pi.appendEntry(HISTORY_ENTRY_TYPE, message);
328
+ return {
329
+ replayed: tail.length,
330
+ total,
331
+ truncated,
332
+ gone: false
333
+ };
334
+ }
335
+ //#endregion
336
+ //#region src/session/session-target.ts
225
337
  function relayTarget(baseUrl, key, workspaceId) {
226
338
  const base = baseUrl.replace(/\/$/, "");
227
339
  return {
@@ -277,7 +389,7 @@ var TargetRef = class {
277
389
  }
278
390
  };
279
391
  //#endregion
280
- //#region src/commands.ts
392
+ //#region src/session/commands.ts
281
393
  function workspaceLabel(workspace) {
282
394
  return `${workspace.external_id} · ${workspace.template.name} · ${workspace.id.slice(0, 8)}`;
283
395
  }
@@ -364,113 +476,7 @@ function registerWorkspaceCommands(pi, deps) {
364
476
  });
365
477
  }
366
478
  //#endregion
367
- //#region src/renderers.ts
368
- const HISTORY_ENTRY_TYPE = "pocketcoder-conversation";
369
- const NOTICE_ENTRY_TYPE = "pocketcoder-history-notice";
370
- function roleHeader(data, theme) {
371
- const time = data.occurred_at.replace("T", " ").replace(/\.\d+Z?$|Z$/, "");
372
- return theme.fg("muted", `${data.role} · ${time}`);
373
- }
374
- function userBody(content, theme) {
375
- const { text, attachments } = splitAttachmentManifest(content);
376
- const body = theme.fg("userMessageText", text);
377
- if (!attachments) return body;
378
- return `${body}\n${attachments.map((attachment) => theme.fg("muted", `⌁ ${attachment.name} (${attachment.size_bytes} bytes)`)).join("\n")}`;
379
- }
380
- function formatConversationMessage(data, theme) {
381
- switch (data.kind ?? "text") {
382
- default: return `${roleHeader(data, theme)}\n${data.role === "user" ? userBody(data.content, theme) : data.role === "assistant" ? data.content : theme.fg("dim", data.content)}`;
383
- }
384
- }
385
- function formatHistoryNotice(data, theme) {
386
- return theme.fg(data.level === "warning" ? "warning" : "muted", data.text);
387
- }
388
- function registerConversationRenderers(pi) {
389
- pi.registerEntryRenderer(HISTORY_ENTRY_TYPE, (entry, _options, theme) => {
390
- if (!entry.data) return void 0;
391
- return new Text(formatConversationMessage(entry.data, theme), 1, 0);
392
- });
393
- pi.registerEntryRenderer(NOTICE_ENTRY_TYPE, (entry, _options, theme) => {
394
- if (!entry.data) return void 0;
395
- return new Text(formatHistoryNotice(entry.data, theme), 1, 0);
396
- });
397
- }
398
- //#endregion
399
- //#region src/history.ts
400
- async function collectTranscriptTail(controlPlane, workspaceId, options) {
401
- const tail = [];
402
- let total = 0;
403
- let pages = 0;
404
- let cursor;
405
- while (pages < options.maxPages) {
406
- const page = await controlPlane.conversations.list(workspaceId, {
407
- cursor,
408
- limit: options.pageLimit
409
- });
410
- pages += 1;
411
- for (const message of page.items) {
412
- total += 1;
413
- tail.push({
414
- role: message.role,
415
- content: message.content,
416
- seq: message.seq,
417
- occurred_at: message.occurred_at,
418
- kind: message.metadata?.kind,
419
- metadata: message.metadata
420
- });
421
- if (tail.length > options.maxMessages) tail.shift();
422
- }
423
- if (page.nextCursor === null) return {
424
- tail,
425
- total,
426
- pages,
427
- exhaustedPages: false
428
- };
429
- cursor = page.nextCursor;
430
- }
431
- return {
432
- tail,
433
- total,
434
- pages,
435
- exhaustedPages: true
436
- };
437
- }
438
- async function replayHistory(pi, controlPlane, workspaceId, options = {}) {
439
- let transcript;
440
- try {
441
- transcript = await collectTranscriptTail(controlPlane, workspaceId, {
442
- pageLimit: options.pageLimit ?? 200,
443
- maxMessages: options.maxMessages ?? 1e3,
444
- maxPages: options.maxPages ?? 50
445
- });
446
- } catch (error) {
447
- if (error instanceof ConversationGoneError) {
448
- pi.appendEntry(NOTICE_ENTRY_TYPE, {
449
- text: error.code === "conversation.deleted" ? "conversation history was deleted" : "conversation history has expired",
450
- level: "warning"
451
- });
452
- return {
453
- replayed: 0,
454
- total: 0,
455
- truncated: false,
456
- gone: true
457
- };
458
- }
459
- throw error;
460
- }
461
- const { tail, total, pages, exhaustedPages } = transcript;
462
- const truncated = total > tail.length || exhaustedPages;
463
- if (truncated) pi.appendEntry(NOTICE_ENTRY_TYPE, { text: exhaustedPages ? `history partially replayed (stopped after ${pages} pages)` : `showing last ${tail.length} of ${total} messages` });
464
- for (const message of tail) pi.appendEntry(HISTORY_ENTRY_TYPE, message);
465
- return {
466
- replayed: tail.length,
467
- total,
468
- truncated,
469
- gone: false
470
- };
471
- }
472
- //#endregion
473
- //#region src/status.ts
479
+ //#region src/ui/status.ts
474
480
  const STATUS_KEY = "pocketcoder";
475
481
  function statusText(workspace) {
476
482
  return `ws ${workspace.id.slice(0, 8)} · ${workspace.state}/${workspace.agent_state}`;
@@ -553,7 +559,7 @@ var StatusPoller = class {
553
559
  }
554
560
  };
555
561
  //#endregion
556
- //#region src/live-session.ts
562
+ //#region src/session/live-session.ts
557
563
  var LiveSession = class {
558
564
  targets;
559
565
  createPoller;
@@ -609,57 +615,7 @@ var LiveSession = class {
609
615
  }
610
616
  };
611
617
  //#endregion
612
- //#region src/response-stream.ts
613
- function textOf(message) {
614
- const content = message.content[0];
615
- return content?.type === "text" ? content.text : "";
616
- }
617
- async function emitRemoteResponse(stream, output, send, onOutputStart = () => {}) {
618
- let previous = "";
619
- let started = false;
620
- const update = (snapshot) => {
621
- if (snapshot === previous) return;
622
- const delta = snapshot.startsWith(previous) ? snapshot.slice(previous.length) : "";
623
- if (!started) {
624
- output.content.push({
625
- type: "text",
626
- text: ""
627
- });
628
- started = true;
629
- onOutputStart();
630
- stream.push({
631
- type: "text_start",
632
- contentIndex: 0,
633
- partial: output
634
- });
635
- }
636
- const content = output.content[0];
637
- if (content?.type === "text") content.text = snapshot;
638
- previous = snapshot;
639
- stream.push({
640
- type: "text_delta",
641
- contentIndex: 0,
642
- delta,
643
- partial: output
644
- });
645
- };
646
- update(await send(update));
647
- if (!started) update("");
648
- stream.push({
649
- type: "text_end",
650
- contentIndex: 0,
651
- content: textOf(output),
652
- partial: output
653
- });
654
- output.stopReason = "stop";
655
- stream.push({
656
- type: "done",
657
- reason: "stop",
658
- message: output
659
- });
660
- }
661
- //#endregion
662
- //#region src/agentapi-events.ts
618
+ //#region src/stream/agentapi-events.ts
663
619
  function isRecord$2(value) {
664
620
  return typeof value === "object" && value !== null;
665
621
  }
@@ -731,7 +687,7 @@ async function* readAgentApiEvents(body, signal) {
731
687
  }
732
688
  }
733
689
  //#endregion
734
- //#region src/client-helpers.ts
690
+ //#region src/client/client-helpers.ts
735
691
  function delay(ms, signal) {
736
692
  return new Promise((resolve, reject) => {
737
693
  const onAbort = () => {
@@ -775,7 +731,7 @@ function changesUrlFor(serviceUrl) {
775
731
  return url.toString();
776
732
  }
777
733
  //#endregion
778
- //#region src/client.ts
734
+ //#region src/client/client.ts
779
735
  function isRecord(value) {
780
736
  return typeof value === "object" && value !== null;
781
737
  }
@@ -978,7 +934,7 @@ var RemoteAgentClient = class {
978
934
  }
979
935
  };
980
936
  //#endregion
981
- //#region src/turn.ts
937
+ //#region src/session/turn.ts
982
938
  function isRetryableTerminal(error) {
983
939
  return error instanceof RemoteRequestError && error.code === "workspace.terminal" && !error.promptAccepted;
984
940
  }
@@ -1023,6 +979,56 @@ async function executeRemoteTurn(options) {
1023
979
  throw new Error("remote turn retry invariant failed");
1024
980
  }
1025
981
  //#endregion
982
+ //#region src/stream/response-stream.ts
983
+ function textOf(message) {
984
+ const content = message.content[0];
985
+ return content?.type === "text" ? content.text : "";
986
+ }
987
+ async function emitRemoteResponse(stream, output, send, onOutputStart = () => {}) {
988
+ let previous = "";
989
+ let started = false;
990
+ const update = (snapshot) => {
991
+ if (snapshot === previous) return;
992
+ const delta = snapshot.startsWith(previous) ? snapshot.slice(previous.length) : "";
993
+ if (!started) {
994
+ output.content.push({
995
+ type: "text",
996
+ text: ""
997
+ });
998
+ started = true;
999
+ onOutputStart();
1000
+ stream.push({
1001
+ type: "text_start",
1002
+ contentIndex: 0,
1003
+ partial: output
1004
+ });
1005
+ }
1006
+ const content = output.content[0];
1007
+ if (content?.type === "text") content.text = snapshot;
1008
+ previous = snapshot;
1009
+ stream.push({
1010
+ type: "text_delta",
1011
+ contentIndex: 0,
1012
+ delta,
1013
+ partial: output
1014
+ });
1015
+ };
1016
+ update(await send(update));
1017
+ if (!started) update("");
1018
+ stream.push({
1019
+ type: "text_end",
1020
+ contentIndex: 0,
1021
+ content: textOf(output),
1022
+ partial: output
1023
+ });
1024
+ output.stopReason = "stop";
1025
+ stream.push({
1026
+ type: "done",
1027
+ reason: "stop",
1028
+ message: output
1029
+ });
1030
+ }
1031
+ //#endregion
1026
1032
  //#region src/extension.ts
1027
1033
  const PROVIDER = "pocketcoder-agentapi";
1028
1034
  const MODEL = "remote-agent";
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "type:app"
7
7
  ]
8
8
  },
9
- "version": "0.3.2",
9
+ "version": "0.3.3",
10
10
  "private": false,
11
11
  "description": "Pi-based terminal UI for PocketCoder workspaces.",
12
12
  "type": "module",
@@ -46,7 +46,7 @@
46
46
  "@earendil-works/pi-ai": "0.83.0",
47
47
  "@earendil-works/pi-coding-agent": "0.83.0",
48
48
  "@earendil-works/pi-tui": "0.83.0",
49
- "@pstdio/pocketcoder-sdk": "^0.5.0"
49
+ "@pstdio/pocketcoder-sdk": "^0.6.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/bun": "1.3.14",