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
|
@@ -3,28 +3,30 @@ import {
|
|
|
3
3
|
assertSecureServerOrigin,
|
|
4
4
|
probeServerAccounts,
|
|
5
5
|
runServerOperation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ywmm63ks.js";
|
|
7
7
|
import {
|
|
8
8
|
loadHostKey,
|
|
9
9
|
loadRelayKey,
|
|
10
10
|
readServerConfig,
|
|
11
11
|
removeServerConfig
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-0xh6cy9v.js";
|
|
13
13
|
import {
|
|
14
14
|
relayHostId
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-7qb9tq54.js";
|
|
16
16
|
import {
|
|
17
17
|
argValue,
|
|
18
18
|
restartService,
|
|
19
19
|
serviceManager,
|
|
20
20
|
withoutRelayArgs
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import"./chunk-
|
|
23
|
-
import"./chunk-
|
|
24
|
-
import"./chunk-
|
|
21
|
+
} from "./chunk-d5ypmmr7.js";
|
|
22
|
+
import"./chunk-kzbzacz9.js";
|
|
23
|
+
import"./chunk-c8nxym2z.js";
|
|
24
|
+
import"./chunk-d93kvxvc.js";
|
|
25
|
+
import"./chunk-n4tjhcb7.js";
|
|
26
|
+
import"./chunk-znhn8af7.js";
|
|
25
27
|
import {
|
|
26
28
|
DEFAULT_DATA_DIR
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-ngf1zatm.js";
|
|
28
30
|
import"./chunk-df32avw6.js";
|
|
29
31
|
|
|
30
32
|
// apps/host/src/cli/leave.ts
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
serveWebClient
|
|
4
4
|
} from "./chunk-r6w5aqkw.js";
|
|
5
|
+
import {
|
|
6
|
+
Published,
|
|
7
|
+
constantValue,
|
|
8
|
+
openHostSettings,
|
|
9
|
+
sameList,
|
|
10
|
+
settingsDefaults
|
|
11
|
+
} from "./chunk-b1h374jr.js";
|
|
5
12
|
import {
|
|
6
13
|
DevicesDoc,
|
|
7
14
|
RevocationsDoc,
|
|
@@ -14,7 +21,7 @@ import {
|
|
|
14
21
|
readServerBinding,
|
|
15
22
|
readServerConfig,
|
|
16
23
|
resolveRelayOrigin
|
|
17
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-0xh6cy9v.js";
|
|
18
25
|
import {
|
|
19
26
|
SECURE_PROLOGUE,
|
|
20
27
|
SecureSession,
|
|
@@ -24,25 +31,25 @@ import {
|
|
|
24
31
|
respondIK,
|
|
25
32
|
startRelayLink,
|
|
26
33
|
toBase64Url
|
|
27
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-7qb9tq54.js";
|
|
28
35
|
import {
|
|
29
36
|
packagedVersion,
|
|
30
37
|
webRoot
|
|
31
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-c8nxym2z.js";
|
|
32
39
|
import {
|
|
33
|
-
HOST_SETTING_KEYS,
|
|
34
40
|
INVALID_FRAME_CLOSE_CODE,
|
|
35
41
|
PROTOCOL,
|
|
36
|
-
THINKING_LEVELS,
|
|
37
42
|
UNAUTHORIZED_CLOSE_CODE,
|
|
38
|
-
isQueueMode,
|
|
39
|
-
isThinkingLevel,
|
|
40
43
|
organize,
|
|
41
44
|
parseClientFrame
|
|
42
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-n4tjhcb7.js";
|
|
46
|
+
import {
|
|
47
|
+
THINKING_LEVELS,
|
|
48
|
+
isThinkingLevel
|
|
49
|
+
} from "./chunk-znhn8af7.js";
|
|
43
50
|
import {
|
|
44
51
|
DEFAULT_DATA_DIR
|
|
45
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-ngf1zatm.js";
|
|
46
53
|
import"./chunk-df32avw6.js";
|
|
47
54
|
|
|
48
55
|
// apps/host/src/main.ts
|
|
@@ -181,7 +188,7 @@ function result(questions, resolution) {
|
|
|
181
188
|
var askUserQuestion = defineTool({
|
|
182
189
|
name: "ask_user_question",
|
|
183
190
|
callers: ["model"],
|
|
184
|
-
description: "Ask the user 1–4 multiple-choice questions when their decision is needed to proceed (ambiguous requirements, choosing between approaches, scope). Blocks until answered. The user can always type a custom answer, so never add an 'Other' option. Keep headers
|
|
191
|
+
description: "Ask the user 1–4 multiple-choice questions when their decision is needed to proceed (ambiguous requirements, choosing between approaches, scope). Blocks until answered. The user can always type a custom answer, so never add an 'Other' option. Keep everything short: headers a word or two, option labels a few words. Usually leave option descriptions out; add one only when the label alone is ambiguous, as one line of at most ~60 characters.",
|
|
185
192
|
parameters: Type2.Object({
|
|
186
193
|
questions: Type2.Array(QuestionItemSchema, { minItems: 1, maxItems: 4 })
|
|
187
194
|
}),
|
|
@@ -522,7 +529,7 @@ var todo = {
|
|
|
522
529
|
|
|
523
530
|
// apps/host/src/host.ts
|
|
524
531
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
525
|
-
import { mkdir as
|
|
532
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
526
533
|
import { basename as basename2, join as join8, sep as sep5 } from "node:path";
|
|
527
534
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
528
535
|
import { createRegistry, Harness } from "@earendil-works/pi-durable";
|
|
@@ -538,7 +545,7 @@ import {
|
|
|
538
545
|
} from "@earendil-works/pi-durable";
|
|
539
546
|
|
|
540
547
|
// apps/host/src/checkout.ts
|
|
541
|
-
import { execFile } from "node:child_process";
|
|
548
|
+
import { execFile, spawn } from "node:child_process";
|
|
542
549
|
import { randomBytes } from "node:crypto";
|
|
543
550
|
import { realpath, rm, stat } from "node:fs/promises";
|
|
544
551
|
import { tmpdir } from "node:os";
|
|
@@ -546,6 +553,42 @@ import { join as join2, relative, sep as sep2 } from "node:path";
|
|
|
546
553
|
import { promisify } from "node:util";
|
|
547
554
|
var run = promisify(execFile);
|
|
548
555
|
var git = (dir, args, env) => run("git", ["-C", dir, ...args], env === undefined ? {} : { env: { ...process.env, ...env } });
|
|
556
|
+
function gitCapped(dir, args, maxBytes, env) {
|
|
557
|
+
return new Promise((resolve, reject) => {
|
|
558
|
+
const child = spawn("git", ["-C", dir, ...args], env === undefined ? {} : { env: { ...process.env, ...env } });
|
|
559
|
+
const chunks = [];
|
|
560
|
+
let size = 0;
|
|
561
|
+
let killed = false;
|
|
562
|
+
let stderr = "";
|
|
563
|
+
child.stdout.on("data", (chunk) => {
|
|
564
|
+
if (killed)
|
|
565
|
+
return;
|
|
566
|
+
chunks.push(chunk);
|
|
567
|
+
size += chunk.length;
|
|
568
|
+
if (size > maxBytes) {
|
|
569
|
+
killed = true;
|
|
570
|
+
child.kill();
|
|
571
|
+
child.stdout.destroy();
|
|
572
|
+
child.stderr.destroy();
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
child.stderr.on("data", (chunk) => {
|
|
576
|
+
if (stderr.length < 8192)
|
|
577
|
+
stderr += chunk.toString("utf8");
|
|
578
|
+
});
|
|
579
|
+
child.on("error", reject);
|
|
580
|
+
child.on("close", (code) => {
|
|
581
|
+
const stdout = Buffer.concat(chunks).toString("utf8");
|
|
582
|
+
if (killed)
|
|
583
|
+
resolve({ stdout, truncated: true });
|
|
584
|
+
else if (code === 0)
|
|
585
|
+
resolve({ stdout, truncated: false });
|
|
586
|
+
else
|
|
587
|
+
reject(Object.assign(new Error(`Command failed: git ${args.join(" ")}
|
|
588
|
+
${stderr}`), { code, stderr }));
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
}
|
|
549
592
|
var worktreeRoot = (dataDir) => join2(dataDir, "worktrees");
|
|
550
593
|
var worktreePath = (dataDir, id) => join2(worktreeRoot(dataDir), String(id));
|
|
551
594
|
var worktreeBranch = (id, suffix) => `pinomad/${id}-${suffix}`;
|
|
@@ -565,6 +608,15 @@ async function gitBase(projectPath) {
|
|
|
565
608
|
return;
|
|
566
609
|
return { repo, subdir: relative(repo, projectPath), base };
|
|
567
610
|
}
|
|
611
|
+
async function branchOf(dir) {
|
|
612
|
+
const ref = await git(dir, ["symbolic-ref", "--quiet", "HEAD"]).then(({ stdout }) => stdout.trim(), () => {
|
|
613
|
+
return;
|
|
614
|
+
});
|
|
615
|
+
const prefix = "refs/heads/";
|
|
616
|
+
if (ref === undefined || !ref.startsWith(prefix))
|
|
617
|
+
return;
|
|
618
|
+
return ref.slice(prefix.length) || undefined;
|
|
619
|
+
}
|
|
568
620
|
var repoLocks = new Map;
|
|
569
621
|
function locked(repo, work) {
|
|
570
622
|
const next = (repoLocks.get(repo) ?? Promise.resolve()).then(work, work);
|
|
@@ -615,12 +667,11 @@ async function changesOf(dir, base, maxBytes = 1024 * 1024) {
|
|
|
615
667
|
try {
|
|
616
668
|
await git(dir, ["read-tree", "HEAD"], env);
|
|
617
669
|
await git(dir, ["add", "-A"], env);
|
|
618
|
-
const { stdout } = await
|
|
619
|
-
if (Buffer.byteLength(stdout) <= maxBytes)
|
|
670
|
+
const { stdout, truncated } = await gitCapped(dir, ["diff", "--cached", "--no-color", "--find-renames", "--src-prefix=a/", "--dst-prefix=b/", base], maxBytes, env);
|
|
671
|
+
if (!truncated && Buffer.byteLength(stdout) <= maxBytes)
|
|
620
672
|
return { base, patch: stdout, truncated: false };
|
|
621
|
-
const cut = stdout.
|
|
622
|
-
const patch = cut.
|
|
623
|
-
`) + 1);
|
|
673
|
+
const cut = Buffer.from(stdout, "utf8").subarray(0, maxBytes);
|
|
674
|
+
const patch = cut.subarray(0, cut.lastIndexOf(10) + 1).toString("utf8");
|
|
624
675
|
return { base, patch, truncated: true };
|
|
625
676
|
} finally {
|
|
626
677
|
await rm(indexFile, { force: true });
|
|
@@ -1196,39 +1247,6 @@ function agentRoleProblem(role) {
|
|
|
1196
1247
|
return;
|
|
1197
1248
|
}
|
|
1198
1249
|
|
|
1199
|
-
// apps/host/src/host-value.ts
|
|
1200
|
-
function constantValue(value) {
|
|
1201
|
-
return {
|
|
1202
|
-
value,
|
|
1203
|
-
subscribe: () => () => {}
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
class Published {
|
|
1208
|
-
#listeners = new Set;
|
|
1209
|
-
#equal;
|
|
1210
|
-
#value;
|
|
1211
|
-
constructor(value, equal) {
|
|
1212
|
-
this.#value = value;
|
|
1213
|
-
this.#equal = equal;
|
|
1214
|
-
}
|
|
1215
|
-
get value() {
|
|
1216
|
-
return this.#value;
|
|
1217
|
-
}
|
|
1218
|
-
subscribe(listener) {
|
|
1219
|
-
this.#listeners.add(listener);
|
|
1220
|
-
return () => this.#listeners.delete(listener);
|
|
1221
|
-
}
|
|
1222
|
-
set(next) {
|
|
1223
|
-
if (this.#equal(this.#value, next))
|
|
1224
|
-
return;
|
|
1225
|
-
this.#value = next;
|
|
1226
|
-
for (const listener of this.#listeners)
|
|
1227
|
-
listener(next);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
var sameList = (a, b, same) => a.length === b.length && a.every((entry, index) => same(entry, b[index]));
|
|
1231
|
-
|
|
1232
1250
|
// apps/host/src/agent-roles.ts
|
|
1233
1251
|
var KNOWN_FIELDS = new Set(["description", "model", "thinkingLevel"]);
|
|
1234
1252
|
async function load(dir) {
|
|
@@ -1382,114 +1400,6 @@ async function openAgentRoles(options) {
|
|
|
1382
1400
|
};
|
|
1383
1401
|
}
|
|
1384
1402
|
|
|
1385
|
-
// apps/host/src/host-settings.ts
|
|
1386
|
-
import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1387
|
-
import { dirname as dirname2 } from "node:path";
|
|
1388
|
-
function settingsDefaults(settings) {
|
|
1389
|
-
return {
|
|
1390
|
-
...settings.defaultModel === null ? {} : { model: settings.defaultModel },
|
|
1391
|
-
...settings.defaultThinkingLevel === null ? {} : { thinkingLevel: settings.defaultThinkingLevel }
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
var isModelRef = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.provider === "string" && value.provider !== "" && typeof value.modelId === "string" && value.modelId !== "";
|
|
1395
|
-
var fileValueError = (key, value, models) => {
|
|
1396
|
-
switch (key) {
|
|
1397
|
-
case "defaultModel":
|
|
1398
|
-
if (!isModelRef(value))
|
|
1399
|
-
return "malformed";
|
|
1400
|
-
return models.some((each) => each.provider === value.provider && each.modelId === value.modelId) ? undefined : "unavailable";
|
|
1401
|
-
case "defaultThinkingLevel":
|
|
1402
|
-
return isThinkingLevel(value) ? undefined : "malformed";
|
|
1403
|
-
case "steeringMode":
|
|
1404
|
-
case "followUpMode":
|
|
1405
|
-
return isQueueMode(value) ? undefined : "malformed";
|
|
1406
|
-
case "acceptAccountDevices":
|
|
1407
|
-
return typeof value === "boolean" ? undefined : "malformed";
|
|
1408
|
-
}
|
|
1409
|
-
};
|
|
1410
|
-
var sameModel = (a, b) => a === b || a !== null && b !== null && a.provider === b.provider && a.modelId === b.modelId;
|
|
1411
|
-
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) && sameList(a.custom, b.custom, (x, y) => x === y);
|
|
1412
|
-
var parse = (path, text) => {
|
|
1413
|
-
let parsed;
|
|
1414
|
-
try {
|
|
1415
|
-
parsed = JSON.parse(text);
|
|
1416
|
-
} catch (error) {
|
|
1417
|
-
throw new Error(`Invalid settings file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1418
|
-
}
|
|
1419
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1420
|
-
throw new Error(`Invalid settings file ${path}: expected a JSON object`);
|
|
1421
|
-
}
|
|
1422
|
-
return parsed;
|
|
1423
|
-
};
|
|
1424
|
-
async function openHostSettings(options) {
|
|
1425
|
-
let file = await readFile3(options.path, "utf8").then((text) => parse(options.path, text), (error) => {
|
|
1426
|
-
if (error.code === "ENOENT")
|
|
1427
|
-
return {};
|
|
1428
|
-
throw new Error(`Cannot read settings file ${options.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1429
|
-
});
|
|
1430
|
-
const warned = new Set;
|
|
1431
|
-
const compute = () => {
|
|
1432
|
-
const fileValues = {};
|
|
1433
|
-
const custom = [];
|
|
1434
|
-
for (const key of HOST_SETTING_KEYS) {
|
|
1435
|
-
if (!Object.hasOwn(file, key))
|
|
1436
|
-
continue;
|
|
1437
|
-
const value = file[key];
|
|
1438
|
-
const invalid = fileValueError(key, value, options.models.value);
|
|
1439
|
-
if (invalid === "malformed" && !warned.has(key)) {
|
|
1440
|
-
warned.add(key);
|
|
1441
|
-
console.error(`${options.path}: ignoring invalid value for settings key "${key}": ${JSON.stringify(value)}`);
|
|
1442
|
-
}
|
|
1443
|
-
if (invalid !== undefined)
|
|
1444
|
-
continue;
|
|
1445
|
-
fileValues[key] = value;
|
|
1446
|
-
custom.push(key);
|
|
1447
|
-
}
|
|
1448
|
-
const { fallback } = options;
|
|
1449
|
-
return {
|
|
1450
|
-
defaultModel: fileValues.defaultModel ?? fallback.defaultModel(),
|
|
1451
|
-
defaultThinkingLevel: fileValues.defaultThinkingLevel ?? fallback.defaultThinkingLevel(),
|
|
1452
|
-
steeringMode: fileValues.steeringMode ?? fallback.steeringMode(),
|
|
1453
|
-
followUpMode: fileValues.followUpMode ?? fallback.followUpMode(),
|
|
1454
|
-
acceptAccountDevices: fileValues.acceptAccountDevices ?? true,
|
|
1455
|
-
custom
|
|
1456
|
-
};
|
|
1457
|
-
};
|
|
1458
|
-
const published = new Published(compute(), sameSettings);
|
|
1459
|
-
options.models.subscribe(() => published.set(compute()));
|
|
1460
|
-
let tail = Promise.resolve();
|
|
1461
|
-
const write = async (patch) => {
|
|
1462
|
-
const model = patch.defaultModel;
|
|
1463
|
-
if (model !== undefined && model !== null && !options.models.value.some((each) => each.provider === model.provider && each.modelId === model.modelId)) {
|
|
1464
|
-
throw new Error(`Unknown model: ${model.provider}/${model.modelId}`);
|
|
1465
|
-
}
|
|
1466
|
-
const next = { ...file };
|
|
1467
|
-
for (const key of HOST_SETTING_KEYS) {
|
|
1468
|
-
const value = patch[key];
|
|
1469
|
-
if (value === undefined)
|
|
1470
|
-
continue;
|
|
1471
|
-
if (value === null)
|
|
1472
|
-
delete next[key];
|
|
1473
|
-
else
|
|
1474
|
-
next[key] = value;
|
|
1475
|
-
}
|
|
1476
|
-
const tmp = `${options.path}.tmp`;
|
|
1477
|
-
await mkdir3(dirname2(options.path), { recursive: true });
|
|
1478
|
-
await writeFile2(tmp, `${JSON.stringify(next, null, 2)}
|
|
1479
|
-
`, { mode: 384 });
|
|
1480
|
-
await rename2(tmp, options.path);
|
|
1481
|
-
file = next;
|
|
1482
|
-
published.set(compute());
|
|
1483
|
-
};
|
|
1484
|
-
return {
|
|
1485
|
-
get value() {
|
|
1486
|
-
return published.value;
|
|
1487
|
-
},
|
|
1488
|
-
subscribe: (listener) => published.subscribe(listener),
|
|
1489
|
-
update: (patch) => tail = tail.then(() => write(patch), () => write(patch))
|
|
1490
|
-
};
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
1403
|
// apps/host/src/host.ts
|
|
1494
1404
|
import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
|
|
1495
1405
|
import lockfile from "proper-lockfile";
|
|
@@ -1549,7 +1459,7 @@ async function listFolders(path, home) {
|
|
|
1549
1459
|
}
|
|
1550
1460
|
|
|
1551
1461
|
// apps/host/src/files.ts
|
|
1552
|
-
import { spawn } from "node:child_process";
|
|
1462
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1553
1463
|
import { opendir, realpath as realpath4, stat as stat4 } from "node:fs/promises";
|
|
1554
1464
|
import { join as join6, resolve as resolve3, sep as sep3 } from "node:path";
|
|
1555
1465
|
import { AgentDoc as AgentDoc3 } from "@earendil-works/pi-durable";
|
|
@@ -1608,7 +1518,7 @@ function createCandidateLister(enumerate, options = {}) {
|
|
|
1608
1518
|
}
|
|
1609
1519
|
function gitLines(cwd, args, limits) {
|
|
1610
1520
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
1611
|
-
const child =
|
|
1521
|
+
const child = spawn2("git", ["-C", cwd, ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1612
1522
|
const chunks = [];
|
|
1613
1523
|
let bytes = 0;
|
|
1614
1524
|
let truncated = false;
|
|
@@ -1656,7 +1566,7 @@ function gitLines(cwd, args, limits) {
|
|
|
1656
1566
|
}
|
|
1657
1567
|
async function inWorkTree(root) {
|
|
1658
1568
|
return await new Promise((resolvePromise) => {
|
|
1659
|
-
const child =
|
|
1569
|
+
const child = spawn2("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
1660
1570
|
let out = "";
|
|
1661
1571
|
child.stdout.on("data", (chunk) => out += chunk.toString("utf8"));
|
|
1662
1572
|
child.on("error", () => resolvePromise(false));
|
|
@@ -1665,7 +1575,7 @@ async function inWorkTree(root) {
|
|
|
1665
1575
|
}
|
|
1666
1576
|
async function hasHead(root) {
|
|
1667
1577
|
return await new Promise((resolvePromise) => {
|
|
1668
|
-
const child =
|
|
1578
|
+
const child = spawn2("git", ["-C", root, "rev-parse", "--verify", "-q", "HEAD"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
1669
1579
|
child.on("error", () => resolvePromise(false));
|
|
1670
1580
|
child.on("close", (code) => resolvePromise(code === 0));
|
|
1671
1581
|
});
|
|
@@ -2479,6 +2389,12 @@ async function startGateway(options) {
|
|
|
2479
2389
|
signingKey: secure.relay.signingKey,
|
|
2480
2390
|
noisePublicKey: secure.hostKey.publicKey,
|
|
2481
2391
|
accept: (socket) => secureHandshake(socket, secure, admission, register),
|
|
2392
|
+
hostName: {
|
|
2393
|
+
get value() {
|
|
2394
|
+
return options.settings.value.hostName;
|
|
2395
|
+
},
|
|
2396
|
+
subscribe: (listener) => options.settings.subscribe((settings) => listener(settings.hostName))
|
|
2397
|
+
},
|
|
2482
2398
|
accountOffer: async () => {
|
|
2483
2399
|
if (!options.settings.value.acceptAccountDevices)
|
|
2484
2400
|
return { ok: false, reason: "disabled" };
|
|
@@ -3456,12 +3372,13 @@ class GatewayClient {
|
|
|
3456
3372
|
if (record !== undefined) {
|
|
3457
3373
|
if (!await worktreeExists(record.path))
|
|
3458
3374
|
return { available: false, reason: "No changes yet" };
|
|
3459
|
-
|
|
3375
|
+
const [changes, branch2] = await Promise.all([changesOf(record.path, record.base), branchOf(record.path)]);
|
|
3376
|
+
return { available: true, ...changes, branch: branch2 };
|
|
3460
3377
|
}
|
|
3461
|
-
const base = await gitBase(cwd);
|
|
3378
|
+
const [base, branch] = await Promise.all([gitBase(cwd), branchOf(cwd)]);
|
|
3462
3379
|
if (base === undefined)
|
|
3463
|
-
return { available: false, reason: "Not in a git repository" };
|
|
3464
|
-
return { available: true, ...await changesOf(base.repo, base.base) };
|
|
3380
|
+
return { available: false, reason: "Not in a git repository", branch };
|
|
3381
|
+
return { available: true, ...await changesOf(base.repo, base.base), branch };
|
|
3465
3382
|
}
|
|
3466
3383
|
async#agent(id) {
|
|
3467
3384
|
return await this.#options.harness.snapshot(AgentDoc4, id, context3) ?? {};
|
|
@@ -3482,16 +3399,16 @@ var context4 = BACKGROUND_CONTEXT4;
|
|
|
3482
3399
|
async function hostToken(dataDir) {
|
|
3483
3400
|
const path = join8(dataDir, "token");
|
|
3484
3401
|
try {
|
|
3485
|
-
return (await
|
|
3402
|
+
return (await readFile3(path, "utf8")).trim();
|
|
3486
3403
|
} catch {
|
|
3487
3404
|
const token = randomBytes3(24).toString("base64url");
|
|
3488
|
-
await
|
|
3405
|
+
await writeFile2(path, `${token}
|
|
3489
3406
|
`, { mode: 384 });
|
|
3490
3407
|
return token;
|
|
3491
3408
|
}
|
|
3492
3409
|
}
|
|
3493
3410
|
async function openHost(options) {
|
|
3494
|
-
await
|
|
3411
|
+
await mkdir3(options.dataDir, { recursive: true });
|
|
3495
3412
|
const stale = Math.max(2000, options.lockStaleMs ?? 1e4);
|
|
3496
3413
|
const release = await lockfile.lock(options.dataDir, {
|
|
3497
3414
|
realpath: false,
|
|
@@ -3551,11 +3468,11 @@ async function openHost(options) {
|
|
|
3551
3468
|
const record = checkoutAt(index?.checkouts, cwd);
|
|
3552
3469
|
if (record !== undefined) {
|
|
3553
3470
|
await ensureWorktree(record);
|
|
3554
|
-
await
|
|
3471
|
+
await mkdir3(cwd, { recursive: true });
|
|
3555
3472
|
}
|
|
3556
3473
|
}
|
|
3557
3474
|
if (cwd.startsWith(`${chatsDir}/`))
|
|
3558
|
-
await
|
|
3475
|
+
await mkdir3(cwd, { recursive: true });
|
|
3559
3476
|
let env = environments.get(cwd);
|
|
3560
3477
|
if (env === undefined) {
|
|
3561
3478
|
env = new NodeExecutionEnv({ cwd });
|
|
@@ -3671,7 +3588,7 @@ async function openHost(options) {
|
|
|
3671
3588
|
|
|
3672
3589
|
// apps/host/src/model-auth.ts
|
|
3673
3590
|
import { existsSync as existsSync2, watch as watch2 } from "node:fs";
|
|
3674
|
-
import { basename as basename3, dirname as
|
|
3591
|
+
import { basename as basename3, dirname as dirname2 } from "node:path";
|
|
3675
3592
|
|
|
3676
3593
|
// apps/host/src/pi-setup.ts
|
|
3677
3594
|
import { getSupportedThinkingLevels as getSupportedThinkingLevels2 } from "@earendil-works/pi-ai";
|
|
@@ -3786,7 +3703,7 @@ function modelSummaries(modelRuntime) {
|
|
|
3786
3703
|
}
|
|
3787
3704
|
|
|
3788
3705
|
// apps/host/src/model-auth.ts
|
|
3789
|
-
var
|
|
3706
|
+
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);
|
|
3790
3707
|
var sameLogin = (a, b) => a.providerId === b.providerId && a.name === b.name && a.kind === b.kind && a.subscription === b.subscription;
|
|
3791
3708
|
async function storedLogins(modelRuntime) {
|
|
3792
3709
|
const credentials = await modelRuntime.listCredentials();
|
|
@@ -3801,7 +3718,7 @@ async function storedLogins(modelRuntime) {
|
|
|
3801
3718
|
}
|
|
3802
3719
|
async function watchModelAuth(modelRuntime, options) {
|
|
3803
3720
|
const debounceMs = options.debounceMs ?? 150;
|
|
3804
|
-
const models = new Published(modelSummaries(modelRuntime), (a, b) => sameList(a, b,
|
|
3721
|
+
const models = new Published(modelSummaries(modelRuntime), (a, b) => sameList(a, b, sameModel));
|
|
3805
3722
|
const auth = new Published(await storedLogins(modelRuntime), (a, b) => sameList(a.logins, b.logins, sameLogin));
|
|
3806
3723
|
const fileName = basename3(options.authPath);
|
|
3807
3724
|
let watcher;
|
|
@@ -3820,7 +3737,7 @@ async function watchModelAuth(modelRuntime, options) {
|
|
|
3820
3737
|
reported = true;
|
|
3821
3738
|
}
|
|
3822
3739
|
};
|
|
3823
|
-
const directory =
|
|
3740
|
+
const directory = dirname2(options.authPath);
|
|
3824
3741
|
if (existsSync2(directory)) {
|
|
3825
3742
|
watcher = watch2(directory, (_event, changed) => {
|
|
3826
3743
|
if (closed || changed !== null && changed !== fileName)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOST_NAME_MAX_UNITS
|
|
3
|
+
} from "./chunk-znhn8af7.js";
|
|
4
|
+
|
|
1
5
|
// packages/protocol/src/relay.ts
|
|
2
6
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
3
7
|
|
|
@@ -332,14 +336,18 @@ function decodeRelayToHost(text) {
|
|
|
332
336
|
throw new Error("unknown relay frame");
|
|
333
337
|
}
|
|
334
338
|
}
|
|
339
|
+
var isFeatureList = (value) => Array.isArray(value) && value.length <= 16 && value.every((entry) => isString2(entry) && entry.length <= 64);
|
|
335
340
|
function decodeRegistered(value) {
|
|
341
|
+
if (value.features !== undefined && !isFeatureList(value.features))
|
|
342
|
+
throw new Error("malformed registered");
|
|
343
|
+
const declared = value.features === undefined ? {} : { features: value.features };
|
|
336
344
|
if (value.push === undefined)
|
|
337
|
-
return { t: "registered" };
|
|
345
|
+
return { t: "registered", ...declared };
|
|
338
346
|
if (!Array.isArray(value.push) || value.push.length > 8 || !value.push.every(isString2)) {
|
|
339
347
|
throw new Error("malformed registered");
|
|
340
348
|
}
|
|
341
349
|
const push = value.push.filter((entry) => entry === "production" || entry === "development");
|
|
342
|
-
return { t: "registered", push };
|
|
350
|
+
return { t: "registered", push, ...declared };
|
|
343
351
|
}
|
|
344
352
|
function decodePushResult(value) {
|
|
345
353
|
if (!isBoundedString(value.requestId) || !isBoundedString(value.reason, 256) || value.result !== "accepted" && value.result !== "unregistered" && value.result !== "retry" && value.result !== "rejected" && value.result !== "unavailable") {
|
|
@@ -388,6 +396,10 @@ function decodeHostToRelay(text) {
|
|
|
388
396
|
throw new Error("malformed pairing-refused");
|
|
389
397
|
}
|
|
390
398
|
return { t: "pairing-refused", requestId: value.requestId, reason: value.reason };
|
|
399
|
+
case "name":
|
|
400
|
+
if (!isBoundedString(value.name, HOST_NAME_MAX_UNITS))
|
|
401
|
+
throw new Error("malformed name");
|
|
402
|
+
return { t: "name", name: value.name };
|
|
391
403
|
default:
|
|
392
404
|
throw new Error("unknown host frame");
|
|
393
405
|
}
|
|
@@ -425,9 +437,8 @@ function decodeRegister(value) {
|
|
|
425
437
|
throw new Error("malformed register");
|
|
426
438
|
if (value.name !== undefined && !isString2(value.name))
|
|
427
439
|
throw new Error("malformed register");
|
|
428
|
-
if (value.features !== undefined &&
|
|
440
|
+
if (value.features !== undefined && !isFeatureList(value.features))
|
|
429
441
|
throw new Error("malformed register");
|
|
430
|
-
}
|
|
431
442
|
return {
|
|
432
443
|
t: "register",
|
|
433
444
|
hostId: value.hostId,
|
|
@@ -511,6 +522,8 @@ function startRelayLink(options) {
|
|
|
511
522
|
let retryTimer;
|
|
512
523
|
let pendingAccepts = 0;
|
|
513
524
|
let pushEnvironments;
|
|
525
|
+
let reportedName;
|
|
526
|
+
let nameDeclared = false;
|
|
514
527
|
const pushes = new Map;
|
|
515
528
|
function setState(next) {
|
|
516
529
|
const changed = state.status !== next.status || state.status === "rejected" && next.status === "rejected" && state.reason !== next.reason;
|
|
@@ -546,6 +559,8 @@ function startRelayLink(options) {
|
|
|
546
559
|
const socket = new WebSocket2(`${wsBase}/host`, { handshakeTimeout: pingIntervalMs });
|
|
547
560
|
control = socket;
|
|
548
561
|
pushEnvironments = undefined;
|
|
562
|
+
nameDeclared = false;
|
|
563
|
+
reportedName = undefined;
|
|
549
564
|
watch(socket);
|
|
550
565
|
socket.on("message", (data, isBinary) => onControlMessage(socket, data, isBinary));
|
|
551
566
|
socket.once("close", (code) => onControlClosed(socket, code));
|
|
@@ -572,21 +587,30 @@ function startRelayLink(options) {
|
|
|
572
587
|
case "challenge": {
|
|
573
588
|
const noisePublicKey = toBase64Url(options.noisePublicKey);
|
|
574
589
|
const challenge = { relayOrigin: origin, hostId, nonce: frame.nonce };
|
|
590
|
+
reportedName = options.hostName?.value;
|
|
575
591
|
socket.send(encodeRelayFrame({
|
|
576
592
|
t: "register",
|
|
577
593
|
hostId,
|
|
578
594
|
signature: signRelayChallenge(options.signingKey.secretKey, challenge),
|
|
579
595
|
signatureV2: signRelayChallengeV2(options.signingKey.secretKey, { ...challenge, noisePublicKey }),
|
|
580
596
|
noisePublicKey,
|
|
581
|
-
...options.accountOffer === undefined ? {} : { features: ["pairing"] }
|
|
597
|
+
...options.accountOffer === undefined ? {} : { features: ["pairing"] },
|
|
598
|
+
...reportedName === undefined ? {} : { name: reportedName }
|
|
582
599
|
}));
|
|
583
600
|
break;
|
|
584
601
|
}
|
|
585
|
-
case "registered":
|
|
602
|
+
case "registered": {
|
|
586
603
|
delay = minDelay;
|
|
587
604
|
pushEnvironments = frame.push;
|
|
605
|
+
nameDeclared = frame.features?.includes("name") === true;
|
|
606
|
+
const current = options.hostName?.value;
|
|
607
|
+
if (nameDeclared && current !== undefined && current !== reportedName) {
|
|
608
|
+
reportedName = current;
|
|
609
|
+
socket.send(encodeRelayFrame({ t: "name", name: current }));
|
|
610
|
+
}
|
|
588
611
|
setState({ status: "registered", ...frame.push === undefined ? {} : { push: frame.push } });
|
|
589
612
|
break;
|
|
613
|
+
}
|
|
590
614
|
case "push-result": {
|
|
591
615
|
const request = pushes.get(frame.requestId);
|
|
592
616
|
if (request === undefined || request.socket !== socket)
|
|
@@ -636,6 +660,8 @@ function startRelayLink(options) {
|
|
|
636
660
|
function onControlClosed(socket, code) {
|
|
637
661
|
control = undefined;
|
|
638
662
|
pushEnvironments = undefined;
|
|
663
|
+
nameDeclared = false;
|
|
664
|
+
reportedName = undefined;
|
|
639
665
|
for (const [requestId, request] of pushes) {
|
|
640
666
|
if (request.socket === socket) {
|
|
641
667
|
pushes.delete(requestId);
|
|
@@ -656,6 +682,15 @@ function startRelayLink(options) {
|
|
|
656
682
|
reconnect(delay);
|
|
657
683
|
delay = Math.min(maxDelay, delay * 2);
|
|
658
684
|
}
|
|
685
|
+
const detachName = options.hostName?.subscribe((name) => {
|
|
686
|
+
if (name === undefined || name === reportedName)
|
|
687
|
+
return;
|
|
688
|
+
const socket = control;
|
|
689
|
+
if (socket === undefined || socket.readyState !== WebSocket2.OPEN || !nameDeclared)
|
|
690
|
+
return;
|
|
691
|
+
reportedName = name;
|
|
692
|
+
socket.send(encodeRelayFrame({ t: "name", name }));
|
|
693
|
+
});
|
|
659
694
|
connectControl();
|
|
660
695
|
return {
|
|
661
696
|
hostId,
|
|
@@ -694,6 +729,7 @@ function startRelayLink(options) {
|
|
|
694
729
|
if (retryTimer !== undefined)
|
|
695
730
|
clearTimeout(retryTimer);
|
|
696
731
|
clearInterval(pingTimer);
|
|
732
|
+
detachName?.();
|
|
697
733
|
for (const socket of watched)
|
|
698
734
|
socket.terminate();
|
|
699
735
|
return Promise.resolve();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connectRemoteDurable
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import"./chunk-
|
|
3
|
+
} from "./chunk-d93kvxvc.js";
|
|
4
|
+
import"./chunk-n4tjhcb7.js";
|
|
5
|
+
import"./chunk-znhn8af7.js";
|
|
5
6
|
import {
|
|
6
7
|
hostAddress
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ngf1zatm.js";
|
|
8
9
|
import"./chunk-df32avw6.js";
|
|
9
10
|
|
|
10
11
|
// apps/host/src/cli/pair.ts
|