pinomad 0.5.0 → 0.6.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/dist/{chunk-ebfaf3k2.js → chunk-0xh6cy9v.js} +1 -1
- package/dist/{chunk-gqr7ahv4.js → chunk-367n9je8.js} +2 -2
- package/dist/{chunk-f0v3sht0.js → chunk-3dnnaz4j.js} +10 -8
- package/dist/{chunk-d4wdt5qs.js → chunk-4btj08we.js} +92 -175
- package/dist/{chunk-kmbh44dt.js → chunk-7qb9tq54.js} +42 -6
- package/dist/{chunk-zttf1hvh.js → chunk-7s1f4z4g.js} +4 -3
- package/dist/chunk-b1h374jr.js +159 -0
- package/dist/{chunk-1d9sppj0.js → chunk-c8nxym2z.js} +1 -1
- package/dist/{chunk-aqj4wg33.js → chunk-d5ypmmr7.js} +29 -12
- package/dist/{chunk-m4q9wafw.js → chunk-d93kvxvc.js} +5 -2
- package/dist/chunk-ggb1taqy.js +16 -0
- package/dist/chunk-jn7y815x.js +79 -0
- package/dist/{chunk-9d59jjmt.js → chunk-k2s1hfq5.js} +36 -10
- package/dist/chunk-kzbzacz9.js +12 -0
- package/dist/{chunk-hsp12cjt.js → chunk-n4tjhcb7.js} +17 -17
- package/dist/{chunk-cjfmfyjb.js → chunk-ngf1zatm.js} +7 -6
- package/dist/{chunk-4by4x5c4.js → chunk-wqvp1fwh.js} +17 -4
- package/dist/{chunk-26d04z02.js → chunk-ywmm63ks.js} +7 -3
- package/dist/chunk-znhn8af7.js +33 -0
- package/dist/pinomad.js +36 -12
- package/package.json +1 -1
- package/web/assets/index-C_aU_ZiV.js +383 -0
- package/web/assets/index-DaPcXZPM.css +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-5ng86fmy.js +0 -14
- package/dist/chunk-crv3dqzp.js +0 -63
- package/web/assets/index-BkF0-im5.css +0 -1
- package/web/assets/index-D4lgm3Y4.js +0 -383
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOST_SETTING_KEYS,
|
|
3
|
+
cleanHostName,
|
|
4
|
+
isQueueMode,
|
|
5
|
+
isThinkingLevel,
|
|
6
|
+
normalizeHostName
|
|
7
|
+
} from "./chunk-znhn8af7.js";
|
|
8
|
+
|
|
9
|
+
// apps/host/src/host-value.ts
|
|
10
|
+
function constantValue(value) {
|
|
11
|
+
return {
|
|
12
|
+
value,
|
|
13
|
+
subscribe: () => () => {}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class Published {
|
|
18
|
+
#listeners = new Set;
|
|
19
|
+
#equal;
|
|
20
|
+
#value;
|
|
21
|
+
constructor(value, equal) {
|
|
22
|
+
this.#value = value;
|
|
23
|
+
this.#equal = equal;
|
|
24
|
+
}
|
|
25
|
+
get value() {
|
|
26
|
+
return this.#value;
|
|
27
|
+
}
|
|
28
|
+
subscribe(listener) {
|
|
29
|
+
this.#listeners.add(listener);
|
|
30
|
+
return () => this.#listeners.delete(listener);
|
|
31
|
+
}
|
|
32
|
+
set(next) {
|
|
33
|
+
if (this.#equal(this.#value, next))
|
|
34
|
+
return;
|
|
35
|
+
this.#value = next;
|
|
36
|
+
for (const listener of this.#listeners)
|
|
37
|
+
listener(next);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var sameList = (a, b, same) => a.length === b.length && a.every((entry, index) => same(entry, b[index]));
|
|
41
|
+
|
|
42
|
+
// apps/host/src/host-settings.ts
|
|
43
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
44
|
+
import { hostname } from "node:os";
|
|
45
|
+
import { dirname } from "node:path";
|
|
46
|
+
function settingsDefaults(settings) {
|
|
47
|
+
return {
|
|
48
|
+
...settings.defaultModel === null ? {} : { model: settings.defaultModel },
|
|
49
|
+
...settings.defaultThinkingLevel === null ? {} : { thinkingLevel: settings.defaultThinkingLevel }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var isModelRef = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.provider === "string" && value.provider !== "" && typeof value.modelId === "string" && value.modelId !== "";
|
|
53
|
+
var fileValueError = (key, value, models) => {
|
|
54
|
+
switch (key) {
|
|
55
|
+
case "defaultModel":
|
|
56
|
+
if (!isModelRef(value))
|
|
57
|
+
return "malformed";
|
|
58
|
+
return models.some((each) => each.provider === value.provider && each.modelId === value.modelId) ? undefined : "unavailable";
|
|
59
|
+
case "defaultThinkingLevel":
|
|
60
|
+
return isThinkingLevel(value) ? undefined : "malformed";
|
|
61
|
+
case "steeringMode":
|
|
62
|
+
case "followUpMode":
|
|
63
|
+
return isQueueMode(value) ? undefined : "malformed";
|
|
64
|
+
case "acceptAccountDevices":
|
|
65
|
+
return typeof value === "boolean" ? undefined : "malformed";
|
|
66
|
+
case "hostName":
|
|
67
|
+
return typeof value === "string" ? undefined : "malformed";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var sameModel = (a, b) => a === b || a !== null && b !== null && a.provider === b.provider && a.modelId === b.modelId;
|
|
71
|
+
var sameSettings = (a, b) => a.steeringMode === b.steeringMode && a.followUpMode === b.followUpMode && a.acceptAccountDevices === b.acceptAccountDevices && a.defaultThinkingLevel === b.defaultThinkingLevel && sameModel(a.defaultModel, b.defaultModel) && a.hostName === b.hostName && sameList(a.custom, b.custom, (x, y) => x === y);
|
|
72
|
+
var parse = (path, text) => {
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(text);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error(`Invalid settings file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
78
|
+
}
|
|
79
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
80
|
+
throw new Error(`Invalid settings file ${path}: expected a JSON object`);
|
|
81
|
+
}
|
|
82
|
+
return parsed;
|
|
83
|
+
};
|
|
84
|
+
async function openHostSettings(options) {
|
|
85
|
+
const read = () => readFile(options.path, "utf8").then((text) => parse(options.path, text), (error) => {
|
|
86
|
+
if (error.code === "ENOENT")
|
|
87
|
+
return {};
|
|
88
|
+
throw new Error(`Cannot read settings file ${options.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
|
+
});
|
|
90
|
+
let file = await read();
|
|
91
|
+
const warned = new Set;
|
|
92
|
+
const compute = () => {
|
|
93
|
+
const fileValues = {};
|
|
94
|
+
const custom = [];
|
|
95
|
+
for (const key of HOST_SETTING_KEYS) {
|
|
96
|
+
if (!Object.hasOwn(file, key))
|
|
97
|
+
continue;
|
|
98
|
+
const value = file[key];
|
|
99
|
+
const invalid = fileValueError(key, value, options.models.value);
|
|
100
|
+
if (invalid === "malformed" && !warned.has(key)) {
|
|
101
|
+
warned.add(key);
|
|
102
|
+
console.error(`${options.path}: ignoring invalid value for settings key "${key}": ${JSON.stringify(value)}`);
|
|
103
|
+
}
|
|
104
|
+
if (invalid !== undefined)
|
|
105
|
+
continue;
|
|
106
|
+
fileValues[key] = value;
|
|
107
|
+
custom.push(key);
|
|
108
|
+
}
|
|
109
|
+
const { fallback } = options;
|
|
110
|
+
return {
|
|
111
|
+
defaultModel: fileValues.defaultModel ?? fallback.defaultModel(),
|
|
112
|
+
defaultThinkingLevel: fileValues.defaultThinkingLevel ?? fallback.defaultThinkingLevel(),
|
|
113
|
+
steeringMode: fileValues.steeringMode ?? fallback.steeringMode(),
|
|
114
|
+
followUpMode: fileValues.followUpMode ?? fallback.followUpMode(),
|
|
115
|
+
acceptAccountDevices: fileValues.acceptAccountDevices ?? true,
|
|
116
|
+
hostName: normalizeHostName(fileValues.hostName ?? hostname()),
|
|
117
|
+
custom
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
const published = new Published(compute(), sameSettings);
|
|
121
|
+
options.models.subscribe(() => published.set(compute()));
|
|
122
|
+
let tail = Promise.resolve();
|
|
123
|
+
const write = async (patch) => {
|
|
124
|
+
const model = patch.defaultModel;
|
|
125
|
+
if (model !== undefined && model !== null && !options.models.value.some((each) => each.provider === model.provider && each.modelId === model.modelId)) {
|
|
126
|
+
throw new Error(`Unknown model: ${model.provider}/${model.modelId}`);
|
|
127
|
+
}
|
|
128
|
+
const name = patch.hostName;
|
|
129
|
+
if (name !== undefined && name !== null && cleanHostName(name) === "") {
|
|
130
|
+
throw new Error(`Invalid host name: ${JSON.stringify(name)}`);
|
|
131
|
+
}
|
|
132
|
+
const next = { ...await read() };
|
|
133
|
+
for (const key of HOST_SETTING_KEYS) {
|
|
134
|
+
const value = patch[key];
|
|
135
|
+
if (value === undefined)
|
|
136
|
+
continue;
|
|
137
|
+
if (value === null)
|
|
138
|
+
delete next[key];
|
|
139
|
+
else
|
|
140
|
+
next[key] = key === "hostName" ? cleanHostName(value) : value;
|
|
141
|
+
}
|
|
142
|
+
const tmp = `${options.path}.tmp`;
|
|
143
|
+
await mkdir(dirname(options.path), { recursive: true });
|
|
144
|
+
await writeFile(tmp, `${JSON.stringify(next, null, 2)}
|
|
145
|
+
`, { mode: 384 });
|
|
146
|
+
await rename(tmp, options.path);
|
|
147
|
+
file = next;
|
|
148
|
+
published.set(compute());
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
get value() {
|
|
152
|
+
return published.value;
|
|
153
|
+
},
|
|
154
|
+
subscribe: (listener) => published.subscribe(listener),
|
|
155
|
+
update: (patch) => tail = tail.then(() => write(patch), () => write(patch))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { constantValue, Published, sameList, settingsDefaults, openHostSettings };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// apps/host/src/distribution.ts
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
var packagedVersion = "0.
|
|
3
|
+
var packagedVersion = "0.6.0";
|
|
4
4
|
var webRoot = fileURLToPath(new URL(packagedVersion === undefined ? "../../web/dist" : "../web/", import.meta.url));
|
|
5
5
|
var hostEntry = packagedVersion === undefined ? { script: fileURLToPath(new URL("./main.ts", import.meta.url)), args: [] } : { script: fileURLToPath(new URL("./pinomad.js", import.meta.url)), args: ["host"] };
|
|
6
6
|
var commandHint = (name) => packagedVersion === undefined ? `bun run ${name}` : `pinomad ${name}`;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isParseArgsError
|
|
3
|
+
} from "./chunk-kzbzacz9.js";
|
|
1
4
|
import {
|
|
2
5
|
commandHint,
|
|
3
6
|
hostEntry,
|
|
4
7
|
packagedVersion
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-c8nxym2z.js";
|
|
6
9
|
import {
|
|
10
|
+
ProtocolMismatchError,
|
|
7
11
|
connectRemoteDurable
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-d93kvxvc.js";
|
|
9
13
|
import {
|
|
10
14
|
DEFAULT_DATA_DIR,
|
|
11
15
|
hostAddress
|
|
12
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ngf1zatm.js";
|
|
13
17
|
|
|
14
18
|
// apps/host/src/cli/service.ts
|
|
15
19
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -375,7 +379,7 @@ function serviceManager(platform = process.platform) {
|
|
|
375
379
|
var run2 = promisify2(execFile2);
|
|
376
380
|
var repoRoot = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
377
381
|
var message = (error) => error instanceof Error ? error.message : String(error);
|
|
378
|
-
function
|
|
382
|
+
function connectToHost(url, token, timeoutMs = 3000) {
|
|
379
383
|
const connecting = connectRemoteDurable({ url, token, reconnectDelayMs: { min: 200, max: 2000 } });
|
|
380
384
|
const timedOut = Symbol("timedOut");
|
|
381
385
|
return Promise.race([
|
|
@@ -416,7 +420,7 @@ async function status(argv) {
|
|
|
416
420
|
}
|
|
417
421
|
try {
|
|
418
422
|
const { url, token } = await hostAddress(argv);
|
|
419
|
-
const remote = await
|
|
423
|
+
const remote = await connectToHost(url, token);
|
|
420
424
|
if (remote.view.current().tasks === undefined)
|
|
421
425
|
await remote.controller.toggleTasks();
|
|
422
426
|
const tasks = remote.view.current().tasks;
|
|
@@ -424,6 +428,8 @@ async function status(argv) {
|
|
|
424
428
|
console.log(`host: reachable at ${url}, ${count} live task${count === 1 ? "" : "s"}`);
|
|
425
429
|
remote.close();
|
|
426
430
|
} catch (error) {
|
|
431
|
+
if (isParseArgsError(error))
|
|
432
|
+
throw error;
|
|
427
433
|
console.log(`host: unreachable (${message(error)})`);
|
|
428
434
|
}
|
|
429
435
|
}
|
|
@@ -431,13 +437,18 @@ async function restartService({
|
|
|
431
437
|
force,
|
|
432
438
|
argv,
|
|
433
439
|
manager = serviceManager(),
|
|
434
|
-
restart
|
|
440
|
+
restart,
|
|
441
|
+
connect = connectToHost,
|
|
442
|
+
pollTimeoutMs = 30000,
|
|
443
|
+
pollDelayMs = 500
|
|
435
444
|
}) {
|
|
436
445
|
if (!(await manager.state()).installed) {
|
|
437
446
|
console.log(`service not installed (${commandHint("service")} install)`);
|
|
438
447
|
return 1;
|
|
439
448
|
}
|
|
440
|
-
const address = await hostAddress(argv).catch(() => {
|
|
449
|
+
const address = await hostAddress(argv).catch((error) => {
|
|
450
|
+
if (isParseArgsError(error))
|
|
451
|
+
throw error;
|
|
441
452
|
return;
|
|
442
453
|
});
|
|
443
454
|
if (!force && address !== undefined) {
|
|
@@ -457,7 +468,7 @@ async function restartService({
|
|
|
457
468
|
console.log("restart requested (host address unknown; not polling)");
|
|
458
469
|
return 0;
|
|
459
470
|
}
|
|
460
|
-
const deadline = Date.now() +
|
|
471
|
+
const deadline = Date.now() + pollTimeoutMs;
|
|
461
472
|
for (;; ) {
|
|
462
473
|
try {
|
|
463
474
|
const remote = await connect(address.url, address.token, 2000);
|
|
@@ -465,11 +476,16 @@ async function restartService({
|
|
|
465
476
|
console.log(`host restarted, reachable at ${address.url}`);
|
|
466
477
|
return 0;
|
|
467
478
|
} catch (error) {
|
|
479
|
+
if (error instanceof ProtocolMismatchError && error.direction === "client-older") {
|
|
480
|
+
const newer = error.hostVersion ?? `protocol major ${error.hostMajor}`;
|
|
481
|
+
console.log(`host restarted (${newer}, newer than this upgrader), reachable at ${address.url}`);
|
|
482
|
+
return 0;
|
|
483
|
+
}
|
|
468
484
|
if (Date.now() > deadline) {
|
|
469
|
-
console.error(`host did not answer within
|
|
485
|
+
console.error(`host did not answer within ${pollTimeoutMs / 1000}s after restart: ${message(error)}`);
|
|
470
486
|
return 1;
|
|
471
487
|
}
|
|
472
|
-
await new Promise((resolve3) => setTimeout(resolve3,
|
|
488
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollDelayMs));
|
|
473
489
|
}
|
|
474
490
|
}
|
|
475
491
|
}
|
|
@@ -512,11 +528,12 @@ async function main(argv) {
|
|
|
512
528
|
process.exitCode = 1;
|
|
513
529
|
}
|
|
514
530
|
} catch (error) {
|
|
531
|
+
if (isParseArgsError(error))
|
|
532
|
+
throw error;
|
|
515
533
|
console.error(message(error));
|
|
516
534
|
process.exitCode = 1;
|
|
517
535
|
}
|
|
518
536
|
}
|
|
519
|
-
if (false)
|
|
520
|
-
;
|
|
537
|
+
if (false) {}
|
|
521
538
|
|
|
522
539
|
export { installSpec, argValue, withoutRelayArgs, serviceManager, restartService, main };
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
hostSupportsCall,
|
|
12
12
|
hostSupportsFeature,
|
|
13
13
|
organize
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-n4tjhcb7.js";
|
|
15
15
|
|
|
16
16
|
// packages/protocol/src/remote-durable.ts
|
|
17
17
|
import { applyImmutable } from "@earendil-works/chord/delta";
|
|
@@ -535,6 +535,9 @@ class RemoteClient {
|
|
|
535
535
|
if (patch.acceptAccountDevices !== undefined && !hostSupportsFeature(this.#hostMinor, "acceptAccountDevices")) {
|
|
536
536
|
throw new Error("The host is too old for this (needs protocol 5.8); run `pinomad upgrade` on the host");
|
|
537
537
|
}
|
|
538
|
+
if (patch.hostName !== undefined && !hostSupportsFeature(this.#hostMinor, "hostName")) {
|
|
539
|
+
throw new Error("The host is too old for this (needs protocol 5.12); run `pinomad upgrade` on the host");
|
|
540
|
+
}
|
|
538
541
|
return this.#call("updateSettings", { patch });
|
|
539
542
|
}),
|
|
540
543
|
createAgent: (role) => this.#request(async () => {
|
|
@@ -604,4 +607,4 @@ class RemoteClient {
|
|
|
604
607
|
}
|
|
605
608
|
}
|
|
606
609
|
|
|
607
|
-
export { connectRemoteDurable };
|
|
610
|
+
export { connectRemoteDurable, ProtocolMismatchError };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
main,
|
|
4
|
+
restartService
|
|
5
|
+
} from "./chunk-d5ypmmr7.js";
|
|
6
|
+
import"./chunk-kzbzacz9.js";
|
|
7
|
+
import"./chunk-c8nxym2z.js";
|
|
8
|
+
import"./chunk-d93kvxvc.js";
|
|
9
|
+
import"./chunk-n4tjhcb7.js";
|
|
10
|
+
import"./chunk-znhn8af7.js";
|
|
11
|
+
import"./chunk-ngf1zatm.js";
|
|
12
|
+
import"./chunk-df32avw6.js";
|
|
13
|
+
export {
|
|
14
|
+
restartService,
|
|
15
|
+
main
|
|
16
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
restartService,
|
|
4
|
+
serviceManager
|
|
5
|
+
} from "./chunk-d5ypmmr7.js";
|
|
6
|
+
import"./chunk-kzbzacz9.js";
|
|
7
|
+
import {
|
|
8
|
+
globalPrefixOf,
|
|
9
|
+
packageDir,
|
|
10
|
+
packagedVersion
|
|
11
|
+
} from "./chunk-c8nxym2z.js";
|
|
12
|
+
import"./chunk-d93kvxvc.js";
|
|
13
|
+
import"./chunk-n4tjhcb7.js";
|
|
14
|
+
import"./chunk-znhn8af7.js";
|
|
15
|
+
import {
|
|
16
|
+
hostAddressOptions
|
|
17
|
+
} from "./chunk-ngf1zatm.js";
|
|
18
|
+
import"./chunk-df32avw6.js";
|
|
19
|
+
|
|
20
|
+
// apps/host/src/cli/upgrade.ts
|
|
21
|
+
import { execFile, spawn } from "node:child_process";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { parseArgs, promisify } from "node:util";
|
|
24
|
+
var exec = promisify(execFile);
|
|
25
|
+
var repoRoot = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
26
|
+
var step = (command, args) => new Promise((resolve) => {
|
|
27
|
+
spawn(command, [...args], { cwd: repoRoot, stdio: "inherit" }).once("exit", (code) => resolve(code ?? 1));
|
|
28
|
+
});
|
|
29
|
+
var serviceInstalled = () => Promise.resolve().then(() => serviceManager().state()).then((state) => state.installed, () => false);
|
|
30
|
+
async function main(argv, deps = {}) {
|
|
31
|
+
const { values } = parseArgs({
|
|
32
|
+
args: [...argv],
|
|
33
|
+
options: { force: { type: "boolean", default: false }, ...hostAddressOptions }
|
|
34
|
+
});
|
|
35
|
+
const run = deps.run ?? ((command, args) => exec(command, args, { cwd: repoRoot }));
|
|
36
|
+
const runStep = deps.step ?? step;
|
|
37
|
+
const restart = deps.restart ?? restartService;
|
|
38
|
+
if (packagedVersion === undefined) {
|
|
39
|
+
const { stdout } = await run("git", ["status", "--porcelain"]);
|
|
40
|
+
if (stdout.trim() !== "") {
|
|
41
|
+
console.error("upgrade: the checkout has uncommitted changes; commit or stash them first");
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const [command, args] of [
|
|
46
|
+
["git", ["pull", "--ff-only"]],
|
|
47
|
+
["bun", ["install"]],
|
|
48
|
+
["bun", ["run", "build"]]
|
|
49
|
+
]) {
|
|
50
|
+
const code = await runStep(command, args);
|
|
51
|
+
if (code !== 0) {
|
|
52
|
+
process.exitCode = code;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
const prefix = globalPrefixOf(packageDir);
|
|
58
|
+
if (prefix === undefined) {
|
|
59
|
+
console.error(`upgrade: pinomad isn't a global npm install (${packageDir}); upgrade it the way it was installed`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const code = await runStep("npm", ["install", "-g", "--prefix", prefix, "pinomad@latest"]);
|
|
64
|
+
if (code !== 0) {
|
|
65
|
+
process.exitCode = code;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!await (deps.serviceInstalled ?? serviceInstalled)()) {
|
|
70
|
+
console.log("service not installed; upgrade done, nothing restarted");
|
|
71
|
+
process.exitCode = 0;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
(deps.exit ?? process.exit)(await restart({ force: values.force, argv: argv.filter((arg) => arg !== "--force") }));
|
|
75
|
+
}
|
|
76
|
+
if (false) {}
|
|
77
|
+
export {
|
|
78
|
+
main
|
|
79
|
+
};
|
|
@@ -3,43 +3,55 @@ import {
|
|
|
3
3
|
assertSecureServerOrigin,
|
|
4
4
|
probeServerAccounts,
|
|
5
5
|
runServerOperation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ywmm63ks.js";
|
|
7
|
+
import {
|
|
8
|
+
constantValue,
|
|
9
|
+
openHostSettings
|
|
10
|
+
} from "./chunk-b1h374jr.js";
|
|
7
11
|
import {
|
|
8
12
|
loadHostKey,
|
|
9
13
|
loadRelayKey,
|
|
10
14
|
writeServerConfig
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-0xh6cy9v.js";
|
|
12
16
|
import {
|
|
13
17
|
relayHostId
|
|
14
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-7qb9tq54.js";
|
|
15
19
|
import {
|
|
16
20
|
argValue,
|
|
17
21
|
installSpec,
|
|
18
22
|
restartService,
|
|
19
23
|
serviceManager,
|
|
20
24
|
withoutRelayArgs
|
|
21
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-d5ypmmr7.js";
|
|
26
|
+
import"./chunk-kzbzacz9.js";
|
|
22
27
|
import {
|
|
23
28
|
commandHint,
|
|
24
29
|
hostEntry
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import"./chunk-
|
|
27
|
-
import"./chunk-
|
|
30
|
+
} from "./chunk-c8nxym2z.js";
|
|
31
|
+
import"./chunk-d93kvxvc.js";
|
|
32
|
+
import"./chunk-n4tjhcb7.js";
|
|
33
|
+
import {
|
|
34
|
+
cleanHostName,
|
|
35
|
+
normalizeHostName
|
|
36
|
+
} from "./chunk-znhn8af7.js";
|
|
28
37
|
import {
|
|
29
38
|
DEFAULT_DATA_DIR
|
|
30
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-ngf1zatm.js";
|
|
31
40
|
import"./chunk-df32avw6.js";
|
|
32
41
|
|
|
33
42
|
// apps/host/src/cli/join.ts
|
|
34
43
|
import { mkdir } from "node:fs/promises";
|
|
35
44
|
import { homedir, hostname } from "node:os";
|
|
36
|
-
import { resolve } from "node:path";
|
|
45
|
+
import { join, resolve } from "node:path";
|
|
37
46
|
import { parseArgs } from "node:util";
|
|
38
47
|
var message = (error) => error instanceof Error ? error.message : String(error);
|
|
39
48
|
async function joinServer(args, deps) {
|
|
40
49
|
const origin = args.origin.replace(/\/+$/, "");
|
|
41
50
|
const dataDir = resolve(args.dataDir);
|
|
42
51
|
assertSecureServerOrigin(origin);
|
|
52
|
+
if (args.name !== undefined && cleanHostName(args.name) === "") {
|
|
53
|
+
throw new Error(`Invalid host name: ${JSON.stringify(args.name)}`);
|
|
54
|
+
}
|
|
43
55
|
await mkdir(dataDir, { recursive: true });
|
|
44
56
|
const signingKey = await loadRelayKey(dataDir);
|
|
45
57
|
const hostKey = await loadHostKey(dataDir);
|
|
@@ -56,13 +68,25 @@ async function joinServer(args, deps) {
|
|
|
56
68
|
if (probe === "disabled") {
|
|
57
69
|
throw new Error(`${origin} does not run server accounts — the server is too old or was started without a data dir`);
|
|
58
70
|
}
|
|
71
|
+
const settings = await openHostSettings({
|
|
72
|
+
path: join(dataDir, "settings.json"),
|
|
73
|
+
fallback: {
|
|
74
|
+
defaultModel: () => null,
|
|
75
|
+
defaultThinkingLevel: () => null,
|
|
76
|
+
steeringMode: () => "one-at-a-time",
|
|
77
|
+
followUpMode: () => "one-at-a-time"
|
|
78
|
+
},
|
|
79
|
+
models: constantValue([])
|
|
80
|
+
});
|
|
81
|
+
const current = settings.value;
|
|
82
|
+
const storedName = current.custom.includes("hostName") ? current.hostName : undefined;
|
|
59
83
|
const reply = await runServerOperation({
|
|
60
84
|
origin,
|
|
61
85
|
signingKey,
|
|
62
86
|
noisePublicKey: hostKey.publicKey,
|
|
63
87
|
op: "join",
|
|
64
88
|
joinToken: args.token,
|
|
65
|
-
name: args.name ?? deps?.hostname ?? hostname()
|
|
89
|
+
name: normalizeHostName(args.name ?? storedName ?? deps?.hostname ?? hostname())
|
|
66
90
|
});
|
|
67
91
|
if (reply.t === "refused") {
|
|
68
92
|
throw new Error(reply.reason === "expired_token" ? "the join token expired — generate a fresh one" : reply.reason === "invalid_token" ? "the join token is invalid or already spent — generate a fresh one" : `the server refused the join: ${reply.reason}`);
|
|
@@ -73,6 +97,8 @@ async function joinServer(args, deps) {
|
|
|
73
97
|
let service;
|
|
74
98
|
try {
|
|
75
99
|
await writeServerConfig(dataDir, { origin });
|
|
100
|
+
if (args.name !== undefined)
|
|
101
|
+
await settings.update({ hostName: args.name });
|
|
76
102
|
const freshSpec = installSpec(["--data-dir", dataDir], {
|
|
77
103
|
execPath: process.execPath,
|
|
78
104
|
env: process.env,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// apps/host/src/cli/args.ts
|
|
2
|
+
function isParseArgsError(error) {
|
|
3
|
+
const code = error.code;
|
|
4
|
+
return typeof code === "string" && code.startsWith("ERR_PARSE_ARGS_");
|
|
5
|
+
}
|
|
6
|
+
function reportParseArgsError(command, error) {
|
|
7
|
+
console.error(`${command}: ${error instanceof Error ? error.message : String(error)}`);
|
|
8
|
+
console.error(`run "${command} --help" for usage`);
|
|
9
|
+
process.exitCode = 2;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { isParseArgsError, reportParseArgsError };
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
followUpMode: true,
|
|
7
|
-
acceptAccountDevices: true
|
|
8
|
-
};
|
|
9
|
-
var HOST_SETTING_KEYS = Object.keys(HOST_SETTING_KEY_SET);
|
|
10
|
-
var THINKING_LEVEL_KEYS = { off: true, minimal: true, low: true, medium: true, high: true, xhigh: true, max: true };
|
|
11
|
-
var THINKING_LEVELS = Object.keys(THINKING_LEVEL_KEYS);
|
|
12
|
-
var isThinkingLevel = (value) => typeof value === "string" && Object.prototype.hasOwnProperty.call(THINKING_LEVEL_KEYS, value);
|
|
13
|
-
var isQueueMode = (value) => value === "all" || value === "one-at-a-time";
|
|
1
|
+
import {
|
|
2
|
+
HOST_SETTING_KEYS,
|
|
3
|
+
isQueueMode,
|
|
4
|
+
isThinkingLevel
|
|
5
|
+
} from "./chunk-znhn8af7.js";
|
|
14
6
|
|
|
15
7
|
// packages/protocol/src/frames.ts
|
|
16
8
|
var HOST_STREAMS = { conversations: 0, index: 0, devices: 0, mcp: 0, models: 1, auth: 1, settings: 2, agents: 6 };
|
|
@@ -22,7 +14,7 @@ var conversationStream = (id) => `conversation:${id}`;
|
|
|
22
14
|
var docStream = (kind, id) => `doc:${kind}:${id}`;
|
|
23
15
|
var UNAUTHORIZED_CLOSE_CODE = 4401;
|
|
24
16
|
var INVALID_FRAME_CLOSE_CODE = 4400;
|
|
25
|
-
var PROTOCOL = { major: 5, minor:
|
|
17
|
+
var PROTOCOL = { major: 5, minor: 12 };
|
|
26
18
|
var CALL_SINCE = {
|
|
27
19
|
logout: 1,
|
|
28
20
|
updateSettings: 2,
|
|
@@ -37,7 +29,14 @@ var CALL_SINCE = {
|
|
|
37
29
|
function hostSupportsCall(hostMinor, method) {
|
|
38
30
|
return hostMinor >= (CALL_SINCE[method] ?? 0);
|
|
39
31
|
}
|
|
40
|
-
var FEATURE_SINCE = {
|
|
32
|
+
var FEATURE_SINCE = {
|
|
33
|
+
images: 5,
|
|
34
|
+
acceptAccountDevices: 8,
|
|
35
|
+
apnsPush: 9,
|
|
36
|
+
agentConversations: 10,
|
|
37
|
+
liveBranch: 11,
|
|
38
|
+
hostName: 12
|
|
39
|
+
};
|
|
41
40
|
function hostSupportsFeature(hostMinor, feature) {
|
|
42
41
|
return hostMinor >= FEATURE_SINCE[feature];
|
|
43
42
|
}
|
|
@@ -101,7 +100,8 @@ var SETTING_VALIDATORS = {
|
|
|
101
100
|
defaultThinkingLevel: (value) => value === null || isThinkingLevel(value),
|
|
102
101
|
steeringMode: (value) => value === null || isQueueMode(value),
|
|
103
102
|
followUpMode: (value) => value === null || isQueueMode(value),
|
|
104
|
-
acceptAccountDevices: (value) => value === null || typeof value === "boolean"
|
|
103
|
+
acceptAccountDevices: (value) => value === null || typeof value === "boolean",
|
|
104
|
+
hostName: (value) => value === null || typeof value === "string"
|
|
105
105
|
};
|
|
106
106
|
function validCallArgs(method, args) {
|
|
107
107
|
const conversationId = () => typeof args.conversationId === "number" && Number.isSafeInteger(args.conversationId) && args.conversationId >= 1;
|
|
@@ -254,4 +254,4 @@ function homeOf(index, summaries, id) {
|
|
|
254
254
|
return;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
export {
|
|
257
|
+
export { HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, organize, checkoutOf, homeOf };
|
|
@@ -4,18 +4,19 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
var DEFAULT_DATA_DIR = process.env.PINOMAD_DATA_DIR ?? join(homedir(), ".pinomad");
|
|
7
|
+
var hostAddressOptions = {
|
|
8
|
+
"data-dir": { type: "string" },
|
|
9
|
+
url: { type: "string", default: "ws://127.0.0.1:7420" },
|
|
10
|
+
token: { type: "string" }
|
|
11
|
+
};
|
|
7
12
|
async function hostAddress(argv) {
|
|
8
13
|
const { values } = parseArgs({
|
|
9
14
|
args: [...argv],
|
|
10
|
-
options:
|
|
11
|
-
"data-dir": { type: "string" },
|
|
12
|
-
url: { type: "string", default: "ws://127.0.0.1:7420" },
|
|
13
|
-
token: { type: "string" }
|
|
14
|
-
}
|
|
15
|
+
options: hostAddressOptions
|
|
15
16
|
});
|
|
16
17
|
const dataDir = resolve(values["data-dir"] ?? DEFAULT_DATA_DIR);
|
|
17
18
|
const token = values.token ?? (await readFile(join(dataDir, "token"), "utf8")).trim();
|
|
18
19
|
return { url: values.url, token, dataDir };
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export { DEFAULT_DATA_DIR, hostAddress };
|
|
22
|
+
export { DEFAULT_DATA_DIR, hostAddressOptions, hostAddress };
|
|
@@ -9,11 +9,14 @@ import {
|
|
|
9
9
|
relayWsBase,
|
|
10
10
|
verifyRelayChallenge,
|
|
11
11
|
verifyRelayChallengeV2
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7qb9tq54.js";
|
|
13
13
|
import {
|
|
14
14
|
commandHint,
|
|
15
15
|
webRoot
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-c8nxym2z.js";
|
|
17
|
+
import {
|
|
18
|
+
normalizeHostName
|
|
19
|
+
} from "./chunk-znhn8af7.js";
|
|
17
20
|
import"./chunk-df32avw6.js";
|
|
18
21
|
|
|
19
22
|
// apps/server/src/main.ts
|
|
@@ -325,7 +328,7 @@ async function openAccounts(options) {
|
|
|
325
328
|
db.exec("ROLLBACK");
|
|
326
329
|
return { status: "refused", reason: failure };
|
|
327
330
|
}
|
|
328
|
-
const hostName = name === undefined
|
|
331
|
+
const hostName = name === undefined ? "Unknown host" : normalizeHostName(name);
|
|
329
332
|
db.prepare(`INSERT INTO hosts (host_id, user_id, noise_public_key, name, joined_at, last_seen_at)
|
|
330
333
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
331
334
|
ON CONFLICT(host_id) DO UPDATE SET
|
|
@@ -358,6 +361,9 @@ async function openAccounts(options) {
|
|
|
358
361
|
markHostSeen(hostId) {
|
|
359
362
|
db.prepare("UPDATE hosts SET last_seen_at = ? WHERE host_id = ?").run(now(), hostId);
|
|
360
363
|
},
|
|
364
|
+
renameHost(hostId, name) {
|
|
365
|
+
db.prepare("UPDATE hosts SET name = ? WHERE host_id = ?").run(normalizeHostName(name), hostId);
|
|
366
|
+
},
|
|
361
367
|
async close() {
|
|
362
368
|
db.close();
|
|
363
369
|
await release();
|
|
@@ -1041,6 +1047,11 @@ async function startRelay(options) {
|
|
|
1041
1047
|
handlePush(socket, hostId, answer);
|
|
1042
1048
|
return;
|
|
1043
1049
|
}
|
|
1050
|
+
if (answer.t === "name") {
|
|
1051
|
+
if (controls.get(hostId)?.socket === socket)
|
|
1052
|
+
accounts?.renameHost(hostId, answer.name);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1044
1055
|
if (answer.t === "pairing" || answer.t === "pairing-refused") {
|
|
1045
1056
|
const request = pairingRequests.get(answer.requestId);
|
|
1046
1057
|
if (request === undefined || request.control !== socket)
|
|
@@ -1097,7 +1108,9 @@ async function startRelay(options) {
|
|
|
1097
1108
|
...frame.features === undefined ? {} : { features: frame.features }
|
|
1098
1109
|
});
|
|
1099
1110
|
accounts?.markHostSeen(hostId);
|
|
1100
|
-
|
|
1111
|
+
if (frame.name !== undefined)
|
|
1112
|
+
accounts?.renameHost(hostId, frame.name);
|
|
1113
|
+
socket.send(encodeRelayFrame({ t: "registered", push: pushEnvironments, features: ["name"] }));
|
|
1101
1114
|
});
|
|
1102
1115
|
socket.once("close", () => {
|
|
1103
1116
|
clearTimeout(deadline);
|