pinomad 0.6.0 → 0.7.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.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  HOST_STREAM_NAMES,
3
3
  INVALID_FRAME_CLOSE_CODE,
4
+ MAX_ATTACHMENTS_PER_MESSAGE,
4
5
  PROTOCOL,
5
6
  UNAUTHORIZED_CLOSE_CODE,
6
7
  checkoutOf,
@@ -11,7 +12,7 @@ import {
11
12
  hostSupportsCall,
12
13
  hostSupportsFeature,
13
14
  organize
14
- } from "./chunk-n4tjhcb7.js";
15
+ } from "./chunk-e4ahjbmz.js";
15
16
 
16
17
  // packages/protocol/src/remote-durable.ts
17
18
  import { applyImmutable } from "@earendil-works/chord/delta";
@@ -59,6 +60,13 @@ class ProtocolMismatchError extends Error {
59
60
  this.direction = direction;
60
61
  }
61
62
  }
63
+ function needsImageRefs(images) {
64
+ return images.some((image) => {
65
+ const data = "data" in image ? image.data : "";
66
+ const ref = "ref" in image ? image.ref : "";
67
+ return data === "" && ref !== "";
68
+ });
69
+ }
62
70
 
63
71
  class RemoteClient {
64
72
  ready;
@@ -79,6 +87,7 @@ class RemoteClient {
79
87
  #nextCall = 1;
80
88
  #nextNotice = 1;
81
89
  #hostMinor = 0;
90
+ #helloReceived = false;
82
91
  #helloModels = [];
83
92
  #helloDefaults = {};
84
93
  #hostVersion;
@@ -98,6 +107,7 @@ class RemoteClient {
98
107
  this.#open();
99
108
  }
100
109
  #open() {
110
+ this.#helloReceived = false;
101
111
  const connection = this.#transport.open({
102
112
  message: (data) => {
103
113
  if (this.#connection === connection)
@@ -173,6 +183,7 @@ class RemoteClient {
173
183
  }
174
184
  const hostMinor = announced !== null && typeof announced === "object" && typeof announced.minor === "number" ? announced.minor : 0;
175
185
  this.#hostMinor = hostMinor;
186
+ this.#helloReceived = true;
176
187
  this.#attempt = 0;
177
188
  this.#docs = frame.docs;
178
189
  this.#toolPresentations = frame.toolPresentations;
@@ -197,7 +208,7 @@ class RemoteClient {
197
208
  for (const stream of this.#conversationStreams(this.#side))
198
209
  this.#wanted.add(stream);
199
210
  for (const stream of this.#wanted)
200
- this.#send({ type: "subscribe", stream });
211
+ this.#subscribe(stream);
201
212
  if (this.#state !== undefined) {
202
213
  this.#update({
203
214
  session: frame.session,
@@ -286,7 +297,8 @@ class RemoteClient {
286
297
  const shown = Promise.all([...missing, ...stale].map((stream) => this.#snapshot(stream)));
287
298
  for (const stream of missing) {
288
299
  this.#wanted.add(stream);
289
- this.#send({ type: "subscribe", stream });
300
+ if (this.#helloReceived)
301
+ this.#subscribe(stream);
290
302
  }
291
303
  await shown;
292
304
  }
@@ -301,6 +313,10 @@ class RemoteClient {
301
313
  this.#send({ type: "unsubscribe", stream });
302
314
  }
303
315
  }
316
+ #subscribe(stream) {
317
+ const refs = stream.startsWith("conversation:") && hostSupportsFeature(this.#hostMinor, "imageRefs");
318
+ this.#send({ type: "subscribe", stream, ...refs ? { images: "ref" } : {} });
319
+ }
304
320
  #host(name) {
305
321
  return this.#values.get(name);
306
322
  }
@@ -380,6 +396,9 @@ class RemoteClient {
380
396
  listener();
381
397
  });
382
398
  }
399
+ #boundSkills(skills) {
400
+ return skills === undefined || !hostSupportsFeature(this.#hostMinor, "boundSkills") ? {} : { skills };
401
+ }
383
402
  #notice(level, message) {
384
403
  if (this.#state === undefined)
385
404
  return;
@@ -440,6 +459,16 @@ class RemoteClient {
440
459
  if (images.length > 0 && !hostSupportsFeature(this.#hostMinor, "images")) {
441
460
  throw new Error("The host is too old for images (needs protocol 5.5); run `pinomad upgrade` on the host");
442
461
  }
462
+ if (needsImageRefs(images) && !hostSupportsFeature(this.#hostMinor, "imageRefs")) {
463
+ throw new Error("The host is too old for image refs (needs protocol 5.15); run `pinomad upgrade` on the host");
464
+ }
465
+ const attachments = options?.attachments ?? [];
466
+ if (attachments.length > MAX_ATTACHMENTS_PER_MESSAGE) {
467
+ throw new Error(`At most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments per message`);
468
+ }
469
+ if (attachments.length > 0 && !hostSupportsFeature(this.#hostMinor, "attachments")) {
470
+ throw new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host");
471
+ }
443
472
  if (options?.agent !== undefined && !hostSupportsFeature(this.#hostMinor, "agentConversations")) {
444
473
  throw new Error("The host is too old for named agents (needs protocol 5.10); run `pinomad upgrade` on the host");
445
474
  }
@@ -451,7 +480,9 @@ class RemoteClient {
451
480
  ...options?.model === undefined ? {} : { model: options.model },
452
481
  ...options?.thinkingLevel === undefined ? {} : { thinkingLevel: options.thinkingLevel },
453
482
  ...options?.agent === undefined ? {} : { agent: options.agent },
454
- ...images.length === 0 ? {} : { images }
483
+ ...images.length === 0 ? {} : { images },
484
+ ...this.#boundSkills(options?.skills),
485
+ ...attachments.length === 0 ? {} : { attachments }
455
486
  });
456
487
  await this.#switch(conversationId2);
457
488
  }),
@@ -467,15 +498,37 @@ class RemoteClient {
467
498
  if (images.length > 0 && !hostSupportsFeature(this.#hostMinor, "images")) {
468
499
  throw new Error("The host is too old for images (needs protocol 5.5); run `pinomad upgrade` on the host");
469
500
  }
501
+ if (needsImageRefs(images) && !hostSupportsFeature(this.#hostMinor, "imageRefs")) {
502
+ throw new Error("The host is too old for image refs (needs protocol 5.15); run `pinomad upgrade` on the host");
503
+ }
504
+ const attachments = options?.attachments ?? [];
505
+ if (attachments.length > MAX_ATTACHMENTS_PER_MESSAGE) {
506
+ throw new Error(`At most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments per message`);
507
+ }
508
+ if (attachments.length > 0 && !hostSupportsFeature(this.#hostMinor, "attachments")) {
509
+ throw new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host");
510
+ }
470
511
  return this.#call("submit", {
471
512
  conversationId: options?.target ?? conversationId(),
472
513
  text,
473
514
  whenBusy,
474
515
  requestId: options?.requestId ?? newRequestId(),
475
- ...images.length === 0 ? {} : { images }
516
+ ...images.length === 0 ? {} : { images },
517
+ ...this.#boundSkills(options?.skills),
518
+ ...attachments.length === 0 ? {} : { attachments }
476
519
  });
477
520
  }),
478
521
  compact: (instructions) => this.#command(() => this.#call("compact", { conversationId: conversationId(), ...instructions === undefined ? {} : { instructions } })),
522
+ runCommand: (name, args, target) => this.#command(() => {
523
+ if (!hostSupportsCall(this.#hostMinor, "runCommand")) {
524
+ throw new Error("The host is too old for this (needs protocol 5.17); run `pinomad upgrade` on the host");
525
+ }
526
+ return this.#call("runCommand", {
527
+ conversationId: target ?? conversationId(),
528
+ name,
529
+ ...args === undefined ? {} : { args }
530
+ });
531
+ }),
479
532
  abort: (target) => {
480
533
  const targetConversation = target ?? this.#current;
481
534
  if (targetConversation === undefined) {
@@ -484,8 +537,34 @@ class RemoteClient {
484
537
  }
485
538
  return this.#call("abort", { conversationId: targetConversation }).then(() => {}, (error) => this.#notice("error", error instanceof Error ? error.message : String(error)));
486
539
  },
540
+ withdrawQueued: (submissionId, target) => this.#request(async () => {
541
+ if (!hostSupportsCall(this.#hostMinor, "withdrawQueued")) {
542
+ throw new Error("The host is too old for this (needs protocol 5.14); run `pinomad upgrade` on the host");
543
+ }
544
+ const { outcome } = await this.#call("withdrawQueued", {
545
+ conversationId: target ?? conversationId(),
546
+ submissionId
547
+ });
548
+ return outcome;
549
+ }),
550
+ steerQueued: (submissionId, target) => this.#request(async () => {
551
+ if (!hostSupportsCall(this.#hostMinor, "steerQueued")) {
552
+ throw new Error("The host is too old for this (needs protocol 5.14); run `pinomad upgrade` on the host");
553
+ }
554
+ const { outcome } = await this.#call("steerQueued", {
555
+ conversationId: target ?? conversationId(),
556
+ submissionId
557
+ });
558
+ return outcome;
559
+ }),
487
560
  setThinkingLevel: (level) => this.#command(() => this.#call("setThinkingLevel", { conversationId: conversationId(), level })),
488
561
  setModel: (model) => this.#command(() => this.#call("setModel", { conversationId: conversationId(), model })),
562
+ setAgent: (agent) => this.#command(() => {
563
+ if (!hostSupportsCall(this.#hostMinor, "setAgent")) {
564
+ throw new Error("The host is too old for this (needs protocol 5.18); run `pinomad upgrade` on the host");
565
+ }
566
+ return this.#call("setAgent", { conversationId: conversationId(), agent });
567
+ }),
489
568
  toggleTasks: () => this.#command(async () => {
490
569
  if (this.#wanted.delete("tasks")) {
491
570
  this.#send({ type: "unsubscribe", stream: "tasks" });
@@ -520,6 +599,36 @@ class RemoteClient {
520
599
  }
521
600
  return this.#call("listFiles", { scope, query });
522
601
  },
602
+ beginAttachment: (name, size) => {
603
+ if (!hostSupportsCall(this.#hostMinor, "beginAttachment")) {
604
+ return Promise.reject(new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host"));
605
+ }
606
+ return this.#call("beginAttachment", { name, size }).then(({ attachmentId }) => attachmentId);
607
+ },
608
+ appendAttachment: (attachmentId, offset, data) => {
609
+ if (!hostSupportsCall(this.#hostMinor, "appendAttachment")) {
610
+ return Promise.reject(new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host"));
611
+ }
612
+ return this.#call("appendAttachment", { attachmentId, offset, data });
613
+ },
614
+ image: (ref) => {
615
+ if (!hostSupportsCall(this.#hostMinor, "image")) {
616
+ return Promise.reject(new Error("The host is too old for this (needs protocol 5.15); run `pinomad upgrade` on the host"));
617
+ }
618
+ return this.#call("image", { ref });
619
+ },
620
+ listSkills: (scope) => {
621
+ if (!hostSupportsCall(this.#hostMinor, "listSkills")) {
622
+ return Promise.reject(new Error("The host is too old for this (needs protocol 5.13); run `pinomad upgrade` on the host"));
623
+ }
624
+ return this.#call("listSkills", { scope });
625
+ },
626
+ listCommands: (id) => {
627
+ if (!hostSupportsCall(this.#hostMinor, "listCommands")) {
628
+ return Promise.reject(new Error("The host is too old for this (needs protocol 5.17); run `pinomad upgrade` on the host"));
629
+ }
630
+ return this.#call("listCommands", { conversationId: id });
631
+ },
523
632
  createPairing: () => this.#request(() => this.#call("createPairing", {})),
524
633
  revokeDevice: (publicKey) => this.#command(() => this.#call("revokeDevice", { publicKey })),
525
634
  logout: (providerId) => this.#command(() => {
@@ -2,15 +2,15 @@
2
2
  import {
3
3
  restartService,
4
4
  serviceManager
5
- } from "./chunk-d5ypmmr7.js";
5
+ } from "./chunk-12kaxggg.js";
6
6
  import"./chunk-kzbzacz9.js";
7
7
  import {
8
8
  globalPrefixOf,
9
9
  packageDir,
10
10
  packagedVersion
11
- } from "./chunk-c8nxym2z.js";
12
- import"./chunk-d93kvxvc.js";
13
- import"./chunk-n4tjhcb7.js";
11
+ } from "./chunk-rtwxjb90.js";
12
+ import"./chunk-sj7rnvh4.js";
13
+ import"./chunk-e4ahjbmz.js";
14
14
  import"./chunk-znhn8af7.js";
15
15
  import {
16
16
  hostAddressOptions
package/dist/pinomad.js CHANGED
@@ -5,22 +5,22 @@ import {
5
5
  } from "./chunk-kzbzacz9.js";
6
6
  import {
7
7
  packagedVersion
8
- } from "./chunk-c8nxym2z.js";
8
+ } from "./chunk-rtwxjb90.js";
9
9
  import {
10
10
  __require
11
11
  } from "./chunk-df32avw6.js";
12
12
 
13
13
  // apps/cli/src/pinomad.ts
14
14
  var COMMANDS = {
15
- host: () => import("./chunk-4btj08we.js"),
16
- server: () => import("./chunk-wqvp1fwh.js"),
17
- relay: () => import("./chunk-wqvp1fwh.js"),
18
- service: () => import("./chunk-ggb1taqy.js"),
19
- upgrade: () => import("./chunk-jn7y815x.js"),
20
- link: () => import("./chunk-367n9je8.js"),
21
- pair: () => import("./chunk-7s1f4z4g.js"),
22
- join: () => import("./chunk-k2s1hfq5.js"),
23
- leave: () => import("./chunk-3dnnaz4j.js")
15
+ host: () => import("./chunk-1nyzr0mg.js"),
16
+ server: () => import("./chunk-6ysezfy5.js"),
17
+ relay: () => import("./chunk-6ysezfy5.js"),
18
+ service: () => import("./chunk-1t58fh0k.js"),
19
+ upgrade: () => import("./chunk-sz3rzk55.js"),
20
+ link: () => import("./chunk-8yme2wep.js"),
21
+ pair: () => import("./chunk-m4c5gvce.js"),
22
+ join: () => import("./chunk-fbkd73zr.js"),
23
+ leave: () => import("./chunk-07769pqx.js")
24
24
  };
25
25
  var USAGE = `pinomad — PiNomad host, server, and utilities
26
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinomad",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "PiNomad — self-hosted coding agent: host, relay, and web client in one package",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",