pinomad 0.8.0 → 0.9.1

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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  serveWebClient
4
- } from "./chunk-r6w5aqkw.js";
4
+ } from "./chunk-wb10efyq.js";
5
5
  import {
6
6
  Published,
7
7
  constantValue,
@@ -21,7 +21,7 @@ import {
21
21
  readServerBinding,
22
22
  readServerConfig,
23
23
  resolveRelayOrigin
24
- } from "./chunk-r2ssa2ex.js";
24
+ } from "./chunk-5gp9svzs.js";
25
25
  import {
26
26
  relayHostId,
27
27
  relayWsBase,
@@ -30,7 +30,7 @@ import {
30
30
  import {
31
31
  packagedVersion,
32
32
  webRoot
33
- } from "./chunk-p56xf1r8.js";
33
+ } from "./chunk-fqgvf8v0.js";
34
34
  import {
35
35
  ATTACHMENT_TTL_MS,
36
36
  INVALID_FRAME_CLOSE_CODE,
@@ -153,7 +153,8 @@ var QuestionDoc = defineDoc({
153
153
  scope: "conversation",
154
154
  history: "latest",
155
155
  fork: "initial",
156
- initial: () => ({ requests: [] })
156
+ initial: () => ({ requests: [] }),
157
+ checkpointWhen: () => false
157
158
  });
158
159
  var resolutionOf = (value, requestId) => value?.requests.find((request) => request.id === requestId)?.resolution;
159
160
  var createWait = (tx, owner, requestId) => tx.createTask(QuestionWait, { requestId }, { ownership: { kind: "task", taskId: owner } });
@@ -472,7 +473,8 @@ var RoleDoc = defineDoc2({
472
473
  scope: "conversation",
473
474
  history: "rewindable",
474
475
  fork: "asOf",
475
- initial: () => ({})
476
+ initial: () => ({}),
477
+ checkpointWhen: () => false
476
478
  });
477
479
 
478
480
  // apps/host/src/organization.ts
@@ -480,7 +482,8 @@ var IndexDoc = defineDoc3({
480
482
  kind: "pinomad.index",
481
483
  version: 1,
482
484
  scope: "session",
483
- initial: () => ({ projects: [], conversations: [] })
485
+ initial: () => ({ projects: [], conversations: [] }),
486
+ checkpointWhen: () => false
484
487
  });
485
488
  async function ensureIndex(harness, context) {
486
489
  await harness.commit(async (tx) => {
@@ -1273,7 +1276,8 @@ var SubagentDoc = defineDoc4({
1273
1276
  scope: "conversation",
1274
1277
  history: "latest",
1275
1278
  fork: "initial",
1276
- initial: () => ({})
1279
+ initial: () => ({}),
1280
+ checkpointWhen: () => false
1277
1281
  });
1278
1282
 
1279
1283
  // apps/host/src/extensions/subagent.ts
@@ -1423,7 +1427,8 @@ var TodoDoc = defineDoc5({
1423
1427
  scope: "conversation",
1424
1428
  history: "rewindable",
1425
1429
  fork: "asOf",
1426
- initial: () => ({ items: [] })
1430
+ initial: () => ({ items: [] }),
1431
+ checkpointWhen: () => false
1427
1432
  });
1428
1433
  var todoWrite = defineTool3({
1429
1434
  name: "todo_write",
@@ -1760,6 +1765,7 @@ function nodeRtcFactory(loaded) {
1760
1765
  let settled = false;
1761
1766
  let deadline;
1762
1767
  let pollTimer;
1768
+ let commitTimer;
1763
1769
  let resolveAnswer;
1764
1770
  let rejectAnswer;
1765
1771
  const answer = new Promise((resolve4, reject) => {
@@ -1782,6 +1788,10 @@ function nodeRtcFactory(loaded) {
1782
1788
  timers.clearTimeout(pollTimer);
1783
1789
  pollTimer = undefined;
1784
1790
  }
1791
+ if (commitTimer !== undefined) {
1792
+ timers.clearTimeout(commitTimer);
1793
+ commitTimer = undefined;
1794
+ }
1785
1795
  };
1786
1796
  const fail = (error) => {
1787
1797
  if (settled)
@@ -1794,12 +1804,18 @@ function nodeRtcFactory(loaded) {
1794
1804
  rejectAnswer(error);
1795
1805
  };
1796
1806
  const commit = () => {
1797
- if (settled || localSdp === undefined)
1807
+ if (settled)
1808
+ return;
1809
+ const local = pc.localDescription();
1810
+ if (local !== null)
1811
+ localSdp = local.sdp;
1812
+ if (localSdp === undefined)
1798
1813
  return;
1799
1814
  for (const candidate of sdpCandidates(localSdp))
1800
1815
  addCandidate(candidate);
1801
1816
  if (candidates.size === 0) {
1802
- fail(new Error("no usable local ICE candidates"));
1817
+ if (deadlineReached)
1818
+ fail(new Error("no usable local ICE candidates"));
1803
1819
  return;
1804
1820
  }
1805
1821
  settled = true;
@@ -1807,18 +1823,37 @@ function nodeRtcFactory(loaded) {
1807
1823
  resolveAnswer(withRtcCandidates(localSdp, [...candidates]));
1808
1824
  };
1809
1825
  const maybeCommit = () => {
1826
+ if (commitTimer !== undefined && !deadlineReached)
1827
+ return;
1810
1828
  if (localSdp !== undefined && (gatheringDone || deadlineReached))
1811
1829
  commit();
1812
1830
  };
1831
+ const scheduleCommit = () => {
1832
+ if (settled)
1833
+ return;
1834
+ if (commitTimer !== undefined)
1835
+ timers.clearTimeout(commitTimer);
1836
+ commitTimer = timers.setTimeout(() => {
1837
+ commitTimer = undefined;
1838
+ maybeCommit();
1839
+ }, 0);
1840
+ };
1813
1841
  pc.onLocalDescription((sdp) => {
1814
1842
  localSdp = sdp;
1815
- maybeCommit();
1843
+ if (gatheringDone)
1844
+ scheduleCommit();
1845
+ else
1846
+ maybeCommit();
1847
+ });
1848
+ pc.onLocalCandidate((candidate) => {
1849
+ addCandidate(candidate);
1850
+ if (gatheringDone)
1851
+ scheduleCommit();
1816
1852
  });
1817
- pc.onLocalCandidate((candidate) => addCandidate(candidate));
1818
1853
  pc.onGatheringStateChange((state) => {
1819
1854
  if (state === "complete") {
1820
1855
  gatheringDone = true;
1821
- maybeCommit();
1856
+ scheduleCommit();
1822
1857
  }
1823
1858
  });
1824
1859
  pc.onStateChange((state) => {
@@ -1855,7 +1890,10 @@ function nodeRtcFactory(loaded) {
1855
1890
  const local = pc.localDescription();
1856
1891
  if (local !== null) {
1857
1892
  localSdp = local.sdp;
1858
- maybeCommit();
1893
+ if (gatheringDone)
1894
+ scheduleCommit();
1895
+ else
1896
+ maybeCommit();
1859
1897
  }
1860
1898
  if (!settled)
1861
1899
  pollTimer = timers.setTimeout(poll, 25);
@@ -2699,7 +2737,8 @@ var TitleDoc = defineDoc6({
2699
2737
  scope: "conversation",
2700
2738
  history: "latest",
2701
2739
  fork: "initial",
2702
- initial: () => ({})
2740
+ initial: () => ({}),
2741
+ checkpointWhen: () => false
2703
2742
  });
2704
2743
 
2705
2744
  // apps/host/src/auto-title.ts
@@ -2875,7 +2914,8 @@ var PushDoc = defineDoc7({
2875
2914
  version: 2,
2876
2915
  scope: "session",
2877
2916
  initial: () => ({ registrations: [] }),
2878
- migrate: () => ({ registrations: [] })
2917
+ migrate: () => ({ registrations: [] }),
2918
+ checkpointWhen: () => false
2879
2919
  });
2880
2920
  var APNS_TOKEN_PATTERN = /^(?:[0-9a-f]{2})+$/;
2881
2921
  var APNS_TOKEN_MAX_CHARS = 256;
@@ -3331,10 +3371,7 @@ async function startGateway(options) {
3331
3371
  return parsed.endpointSet;
3332
3372
  };
3333
3373
  const currentEndpointSet = () => checkedEndpointSet(currentAdvertisement().endpoints);
3334
- const register = (connection, devicePublicKey, joined) => {
3335
- if (joined?.source === "account") {
3336
- broadcast("info", `New device "${joined.name}" joined through your account`);
3337
- }
3374
+ const register = (connection, devicePublicKey) => {
3338
3375
  const client = new GatewayClient(connection, options, conversations, fileLister, skillLister, imageVariants, {
3339
3376
  devicePublicKey,
3340
3377
  rtcHello,
@@ -6212,7 +6249,7 @@ var CUSTOM_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
6212
6249
  var LOGIN_TIMEOUT_MS = 15 * 60000;
6213
6250
  var FINISHED_LOGIN_TTL_MS = 60000;
6214
6251
  var ENDPOINT_TIMEOUT_MS = 1e4;
6215
- var sameModel = (a, b) => a.provider === b.provider && a.modelId === b.modelId && a.name === b.name && a.contextWindow === b.contextWindow && sameList(a.thinkingLevels, b.thinkingLevels, (x, y) => x === y);
6252
+ var sameModel = (a, b) => a.provider === b.provider && a.modelId === b.modelId && a.name === b.name && a.contextWindow === b.contextWindow && a.images === b.images && sameList(a.thinkingLevels, b.thinkingLevels, (x, y) => x === y);
6216
6253
  var sameLogin = (a, b) => a.providerId === b.providerId && a.name === b.name && a.kind === b.kind && a.subscription === b.subscription;
6217
6254
  async function readModelsFile(path) {
6218
6255
  let text;
@@ -6483,6 +6520,8 @@ async function openModelAuth(runtime, options) {
6483
6520
  const ids = [...new Set(input.models.map((model) => model.trim()).filter((model) => model !== ""))];
6484
6521
  if (ids.length === 0)
6485
6522
  throw new Error("Add at least one model");
6523
+ const key = typeof apiKey === "string" && apiKey !== "" ? apiKey : apiKey !== null && custom.has(input.id) ? (await runtime.getAuth(input.id))?.auth.apiKey : undefined;
6524
+ const reported = new Map((await fetchEndpointModels(input.api, input.baseUrl, key).catch(() => [])).map((model) => [model.id, model]));
6486
6525
  await updateModelsFile((file) => {
6487
6526
  const previous = file.providers[input.id] ?? {};
6488
6527
  const previousModels = new Map((Array.isArray(previous.models) ? previous.models : []).map((model) => [model.id, model]));
@@ -6491,7 +6530,7 @@ async function openModelAuth(runtime, options) {
6491
6530
  name,
6492
6531
  api: input.api,
6493
6532
  baseUrl: input.baseUrl,
6494
- models: ids.map((id) => previousModels.get(id) ?? { id })
6533
+ models: ids.map((id) => ({ ...reported.get(id), ...previousModels.get(id) ?? { id } }))
6495
6534
  };
6496
6535
  });
6497
6536
  await runtime.refresh({ providers: [input.id], allowNetwork: false });
@@ -6518,7 +6557,7 @@ async function openModelAuth(runtime, options) {
6518
6557
  if (key === undefined && input.providerId !== undefined && custom.has(input.providerId)) {
6519
6558
  key = (await runtime.getAuth(input.providerId))?.auth.apiKey;
6520
6559
  }
6521
- return fetchEndpointModels(input.api, input.baseUrl, key);
6560
+ return (await fetchEndpointModels(input.api, input.baseUrl, key)).map((model) => model.id);
6522
6561
  },
6523
6562
  close() {
6524
6563
  closed = true;
@@ -6556,10 +6595,34 @@ async function fetchEndpointModels(api, baseUrl, key) {
6556
6595
  if (!response.ok)
6557
6596
  throw new Error(`${url.host} answered ${response.status} for /models`);
6558
6597
  const body = await response.json();
6559
- const ids = api === "google-generative-ai" ? (body.models ?? []).flatMap((model) => typeof model.name === "string" ? [model.name.replace(/^models\//, "")] : []) : (body.data ?? []).flatMap((model) => typeof model.id === "string" ? [model.id] : []);
6560
- if (ids.length === 0)
6598
+ const models = api === "google-generative-ai" ? (body.models ?? []).flatMap((model) => typeof model.name === "string" ? [
6599
+ endpointModel(model.name.replace(/^models\//, ""), {
6600
+ contextWindow: positive(model.inputTokenLimit),
6601
+ maxTokens: positive(model.outputTokenLimit),
6602
+ reasoning: model.thinking === true,
6603
+ image: false
6604
+ })
6605
+ ] : []) : (body.data ?? []).flatMap((model) => typeof model.id === "string" ? [
6606
+ endpointModel(model.id, {
6607
+ contextWindow: positive(model.context_window) ?? positive(model.context_length) ?? positive(model.max_input_tokens),
6608
+ maxTokens: positive(model.max_output_tokens),
6609
+ reasoning: model.reasoning === true,
6610
+ image: Array.isArray(model.modalities?.input) && model.modalities.input.includes("image")
6611
+ })
6612
+ ] : []);
6613
+ if (models.length === 0)
6561
6614
  throw new Error(`${url.host} listed no models`);
6562
- return ids;
6615
+ return models;
6616
+ }
6617
+ var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
6618
+ function endpointModel(id, reported) {
6619
+ return {
6620
+ id,
6621
+ ...reported.contextWindow === undefined ? {} : { contextWindow: reported.contextWindow },
6622
+ ...reported.maxTokens === undefined ? {} : { maxTokens: reported.maxTokens },
6623
+ ...reported.reasoning ? { reasoning: true } : {},
6624
+ ...reported.image ? { input: ["text", "image"] } : {}
6625
+ };
6563
6626
  }
6564
6627
 
6565
6628
  // apps/host/src/main.ts
@@ -9,7 +9,7 @@ import {
9
9
  loadRelayKey,
10
10
  readServerConfig,
11
11
  removeServerConfig
12
- } from "./chunk-r2ssa2ex.js";
12
+ } from "./chunk-5gp9svzs.js";
13
13
  import {
14
14
  relayHostId
15
15
  } from "./chunk-khcpv54k.js";
@@ -18,10 +18,10 @@ import {
18
18
  restartService,
19
19
  serviceManager,
20
20
  withoutRelayArgs
21
- } from "./chunk-73vxwjs7.js";
21
+ } from "./chunk-32g3m2h0.js";
22
22
  import"./chunk-kzbzacz9.js";
23
- import"./chunk-p56xf1r8.js";
24
- import"./chunk-a4b61tx5.js";
23
+ import"./chunk-fqgvf8v0.js";
24
+ import"./chunk-4sepcfpy.js";
25
25
  import"./chunk-jgd38rna.js";
26
26
  import"./chunk-sygeb9qb.js";
27
27
  import {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  connectRemoteDurable
3
- } from "./chunk-a4b61tx5.js";
3
+ } from "./chunk-4sepcfpy.js";
4
4
  import"./chunk-jgd38rna.js";
5
5
  import {
6
6
  isLoopbackUrl
@@ -13,7 +13,24 @@ var WEB_TYPES = {
13
13
  ".woff2": "font/woff2"
14
14
  };
15
15
  var NOT_BUILT = "Web client not built: run bun run build";
16
+ var SECURITY_HEADERS = {
17
+ "content-security-policy": [
18
+ "default-src 'self'",
19
+ "script-src 'self'",
20
+ "style-src 'self' 'unsafe-inline'",
21
+ "img-src 'self' data:",
22
+ "connect-src 'self' ws: wss:",
23
+ "object-src 'none'",
24
+ "base-uri 'self'",
25
+ "form-action 'self'",
26
+ "frame-ancestors 'none'"
27
+ ].join("; "),
28
+ "x-content-type-options": "nosniff",
29
+ "referrer-policy": "no-referrer"
30
+ };
16
31
  async function serveWebClient(request, response, webRoot) {
32
+ for (const [name, value] of Object.entries(SECURITY_HEADERS))
33
+ response.setHeader(name, value);
17
34
  if (request.method !== "GET" && request.method !== "HEAD") {
18
35
  response.writeHead(405).end();
19
36
  return;
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-p56xf1r8.js";
8
+ } from "./chunk-fqgvf8v0.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-jjtr7fr5.js"),
16
- server: () => import("./chunk-1r8a2434.js"),
17
- relay: () => import("./chunk-1r8a2434.js"),
18
- service: () => import("./chunk-ptjyk94f.js"),
19
- upgrade: () => import("./chunk-fes7wjw1.js"),
20
- link: () => import("./chunk-p15hqqjz.js"),
21
- pair: () => import("./chunk-mv94ksvb.js"),
22
- join: () => import("./chunk-cytnycs9.js"),
23
- leave: () => import("./chunk-ddh6wfv2.js")
15
+ host: () => import("./chunk-n8cz7skh.js"),
16
+ server: () => import("./chunk-cx8jgpdt.js"),
17
+ relay: () => import("./chunk-cx8jgpdt.js"),
18
+ service: () => import("./chunk-3gcpzpjq.js"),
19
+ upgrade: () => import("./chunk-azarj0f4.js"),
20
+ link: () => import("./chunk-029mmh7d.js"),
21
+ pair: () => import("./chunk-vax4zspw.js"),
22
+ join: () => import("./chunk-n58m5hgh.js"),
23
+ leave: () => import("./chunk-nj5gz6qh.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.8.0",
3
+ "version": "0.9.1",
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",
@@ -20,12 +20,12 @@
20
20
  },
21
21
  "homepage": "https://github.com/bubbleptr/pinomad",
22
22
  "dependencies": {
23
- "@earendil-works/chord": "1.2.0",
24
- "@earendil-works/pi-ai": "1.2.0",
25
- "@earendil-works/pi-codemode": "1.2.0",
26
- "@earendil-works/pi-coding-agent": "1.2.0",
27
- "@earendil-works/pi-durable": "1.2.0",
28
- "@earendil-works/pi-mcp": "1.2.0",
23
+ "@earendil-works/chord": "1.4.0",
24
+ "@earendil-works/pi-ai": "1.4.0",
25
+ "@earendil-works/pi-codemode": "1.4.0",
26
+ "@earendil-works/pi-coding-agent": "1.4.0",
27
+ "@earendil-works/pi-durable": "1.4.0",
28
+ "@earendil-works/pi-mcp": "1.4.0",
29
29
  "@noble/curves": "2.4.0",
30
30
  "diff": "8.0.4",
31
31
  "node-datachannel": "0.33.4",
@@ -35,6 +35,9 @@
35
35
  "uqr": "0.1.3",
36
36
  "ws": "8.22.0",
37
37
  "@noble/ciphers": "2.4.0",
38
- "@noble/hashes": "2.4.0"
38
+ "@noble/hashes": "2.4.0",
39
+ "@earendil-works/pi-agent-core": "1.4.0",
40
+ "@earendil-works/pi-telemetry": "1.4.0",
41
+ "@earendil-works/pi-tui": "1.4.0"
39
42
  }
40
43
  }