pinomad 0.1.1 → 0.2.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/README.md +4 -2
- package/dist/{chunk-9z7echn7.js → chunk-5712fvyp.js} +4 -4
- package/dist/{chunk-y73t2jmc.js → chunk-5h97zxag.js} +119 -41
- package/dist/{chunk-zx77w1nm.js → chunk-d9q0dz8s.js} +2 -2
- package/dist/{chunk-mqbg2cbw.js → chunk-fhtkkcgt.js} +390 -158
- package/dist/{chunk-wgn3cx5q.js → chunk-h35fermn.js} +48 -12
- package/dist/{chunk-tpd3trvd.js → chunk-haycqqp1.js} +1 -1
- package/dist/{chunk-8h3wrn84.js → chunk-n4bvmcy8.js} +4 -4
- package/dist/{chunk-hsdzb73r.js → chunk-q26n39mf.js} +10 -4
- package/dist/{chunk-ne6a5q2j.js → chunk-qdxhqva8.js} +1 -1
- package/dist/{chunk-fpb6f9n5.js → chunk-rkfdsacq.js} +1 -1
- package/dist/pinomad.js +7 -7
- package/package.json +1 -1
- package/web/assets/index-4p91UVoc.js +383 -0
- package/web/assets/index-KwICnYux.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-B9yvK0AN.js +0 -383
- package/web/assets/index-t5dAiohJ.css +0 -1
|
@@ -5,12 +5,13 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
packagedVersion,
|
|
7
7
|
webRoot
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-rkfdsacq.js";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
INVALID_FRAME_CLOSE_CODE,
|
|
11
|
+
PROTOCOL,
|
|
11
12
|
UNAUTHORIZED_CLOSE_CODE,
|
|
12
|
-
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
parseClientFrame
|
|
14
|
+
} from "./chunk-h35fermn.js";
|
|
14
15
|
import {
|
|
15
16
|
DevicesDoc,
|
|
16
17
|
ensureDevices,
|
|
@@ -619,6 +620,7 @@ import { dirname, join, sep } from "node:path";
|
|
|
619
620
|
import { defineExtension as defineExtension3, section as section2 } from "@earendil-works/pi-durable";
|
|
620
621
|
import { formatSkillsForPrompt, loadProjectContextFiles, loadSkills } from "@earendil-works/pi-coding-agent";
|
|
621
622
|
var PREAMBLE = "You are PiNomad, a coding agent running on the user's own machine. You work in the project directory below with the read, write, edit, and bash tools: read files before changing them, prefer edit over rewriting whole files, and run commands to check your work. Keep answers short and concrete. When it is unclear what the user wants, ask instead of guessing.";
|
|
623
|
+
var FORK_SHARED_DIR = "This conversation is a fork: it shares this working directory with the conversation it was forked from, which may be editing the same files. If your task needs isolation, create your own git worktree (for example `git worktree add <dir> -b <branch>`) and use absolute paths inside it for every read, write, edit and bash command.";
|
|
622
624
|
function createContext(options) {
|
|
623
625
|
const { agentsHome } = options;
|
|
624
626
|
const resolveCheckout = (input, context) => options.checkout?.(input, context);
|
|
@@ -631,14 +633,19 @@ function createContext(options) {
|
|
|
631
633
|
const cwd = input.agent.cwd;
|
|
632
634
|
if (cwd === undefined)
|
|
633
635
|
return;
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
636
|
+
const resolved = await resolveCheckout({ conversationId: input.conversationId, read: input.read }, context);
|
|
637
|
+
const checkout = resolved?.checkout;
|
|
638
|
+
const forkLine = resolved?.fork === true && checkout?.own !== true ? [FORK_SHARED_DIR] : [];
|
|
639
|
+
if (checkout === undefined) {
|
|
640
|
+
return [`Working directory: ${cwd}`, ...forkLine].join(`
|
|
641
|
+
`);
|
|
642
|
+
}
|
|
637
643
|
return [
|
|
638
644
|
`Working directory: ${cwd}`,
|
|
639
|
-
`This directory is a git worktree PiNomad created for this conversation, on branch ${checkout.branch} starting from commit ${checkout.base.slice(0, 12)}.`,
|
|
645
|
+
checkout.own ? `This directory is a git worktree PiNomad created for this conversation, on branch ${checkout.branch} starting from commit ${checkout.base.slice(0, 12)}.` : `This directory is a git worktree PiNomad created for the conversation this one belongs to, on branch ${checkout.branch} starting from commit ${checkout.base.slice(0, 12)}.`,
|
|
640
646
|
`The user's project directory is ${checkout.projectPath} and must not be modified.`,
|
|
641
|
-
"This is a fresh checkout: ignored files (dependencies such as node_modules, or .env) are absent — install dependencies per AGENTS.md when needed."
|
|
647
|
+
"This is a fresh checkout: ignored files (dependencies such as node_modules, or .env) are absent — install dependencies per AGENTS.md when needed.",
|
|
648
|
+
...forkLine
|
|
642
649
|
].join(`
|
|
643
650
|
`);
|
|
644
651
|
}),
|
|
@@ -646,7 +653,7 @@ function createContext(options) {
|
|
|
646
653
|
const cwd = input.agent.cwd;
|
|
647
654
|
if (cwd === undefined)
|
|
648
655
|
return;
|
|
649
|
-
const checkout = await resolveCheckout({ conversationId: input.conversationId, read: input.read }, context);
|
|
656
|
+
const checkout = (await resolveCheckout({ conversationId: input.conversationId, read: input.read }, context))?.checkout;
|
|
650
657
|
let files;
|
|
651
658
|
if (checkout === undefined) {
|
|
652
659
|
files = loadProjectContextFiles({ cwd, agentDir: agentsHome });
|
|
@@ -696,6 +703,19 @@ import {
|
|
|
696
703
|
defineTool as defineTool3
|
|
697
704
|
} from "@earendil-works/pi-durable";
|
|
698
705
|
import { Type as Type4 } from "typebox";
|
|
706
|
+
|
|
707
|
+
// apps/host/src/subagent-doc.ts
|
|
708
|
+
import { defineDoc as defineDoc2 } from "@earendil-works/pi-durable";
|
|
709
|
+
var SubagentDoc = defineDoc2({
|
|
710
|
+
kind: "pinomad.subagent",
|
|
711
|
+
version: 1,
|
|
712
|
+
scope: "conversation",
|
|
713
|
+
history: "latest",
|
|
714
|
+
fork: "initial",
|
|
715
|
+
initial: () => ({})
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// apps/host/src/extensions/subagent.ts
|
|
699
719
|
var INSTRUCTIONS = "You are a subagent working on one delegated task. You cannot ask the user questions; if you need a decision or clarification, stop and state exactly what you need in your final answer. Your final message is returned to the delegating agent as the result, so make it a complete, self-contained report.";
|
|
700
720
|
var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
701
721
|
function createSubagent(options) {
|
|
@@ -716,8 +736,14 @@ function createSubagent(options) {
|
|
|
716
736
|
execute: async (args, api, context) => {
|
|
717
737
|
const child = await api.commit(async (tx) => {
|
|
718
738
|
const existing = (await tx.scanConversations({ ownerTaskId: api.taskId }, 1)).items[0];
|
|
719
|
-
if (existing !== undefined)
|
|
720
|
-
|
|
739
|
+
if (existing !== undefined) {
|
|
740
|
+
const doc2 = await tx.doc(SubagentDoc, existing.id);
|
|
741
|
+
return {
|
|
742
|
+
id: existing.id,
|
|
743
|
+
...doc2?.model === undefined ? {} : { model: doc2.model },
|
|
744
|
+
...doc2?.startedAt === undefined ? {} : { startedAt: doc2.startedAt }
|
|
745
|
+
};
|
|
746
|
+
}
|
|
721
747
|
let model;
|
|
722
748
|
let thinkingLevel;
|
|
723
749
|
let override;
|
|
@@ -748,12 +774,22 @@ function createSubagent(options) {
|
|
|
748
774
|
};
|
|
749
775
|
const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
|
750
776
|
await configure(tx, created.id, change);
|
|
751
|
-
|
|
777
|
+
const childAgent = await tx.doc(AgentDoc, created.id);
|
|
778
|
+
const ref = childAgent.model ?? model ?? (await tx.doc(AgentDoc, api.conversationId)).model;
|
|
779
|
+
const startedAt = Date.now();
|
|
780
|
+
const doc = await tx.doc(SubagentDoc, created.id);
|
|
781
|
+
doc.startedAt = startedAt;
|
|
782
|
+
if (ref !== undefined)
|
|
783
|
+
doc.model = `${ref.provider}/${ref.modelId}`;
|
|
784
|
+
const label = args.description?.trim();
|
|
785
|
+
if (label !== undefined && label !== "")
|
|
786
|
+
doc.label = label;
|
|
787
|
+
return { id: created.id, ...ref === undefined ? {} : { model: doc.model }, startedAt };
|
|
752
788
|
}, context);
|
|
753
|
-
await api.details({ conversationId: child }, context);
|
|
754
|
-
const handle = await api.conversation(child, context);
|
|
789
|
+
await api.details({ conversationId: child.id, ...child.model === undefined ? {} : { model: child.model }, ...child.startedAt === undefined ? {} : { startedAt: child.startedAt } }, context);
|
|
790
|
+
const handle = await api.conversation(child.id, context);
|
|
755
791
|
if (handle === undefined)
|
|
756
|
-
throw new Error(`Subagent conversation ${child} does not exist`);
|
|
792
|
+
throw new Error(`Subagent conversation ${child.id} does not exist`);
|
|
757
793
|
const request = { type: "input", content: args.task, requestId: `subagent:${api.taskId}` };
|
|
758
794
|
const settled = await (await handle.submit(request, context)).wait(context);
|
|
759
795
|
if (settled.status !== "done" || settled.type !== "input")
|
|
@@ -763,7 +799,11 @@ function createSubagent(options) {
|
|
|
763
799
|
const text = message?.role === "assistant" ? message.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("") : "";
|
|
764
800
|
const result2 = {
|
|
765
801
|
content: [{ type: "text", text: text === "" ? "(The subagent returned no text.)" : text }],
|
|
766
|
-
details: {
|
|
802
|
+
details: {
|
|
803
|
+
conversationId: child.id,
|
|
804
|
+
...child.model === undefined ? {} : { model: child.model },
|
|
805
|
+
...child.startedAt === undefined ? {} : { startedAt: child.startedAt }
|
|
806
|
+
}
|
|
767
807
|
};
|
|
768
808
|
return result2;
|
|
769
809
|
}
|
|
@@ -773,9 +813,9 @@ function createSubagent(options) {
|
|
|
773
813
|
}
|
|
774
814
|
|
|
775
815
|
// apps/host/src/extensions/todo.ts
|
|
776
|
-
import { defineDoc as
|
|
816
|
+
import { defineDoc as defineDoc3, defineExtension as defineExtension5, defineTool as defineTool4 } from "@earendil-works/pi-durable";
|
|
777
817
|
import { Type as Type5 } from "typebox";
|
|
778
|
-
var TodoDoc =
|
|
818
|
+
var TodoDoc = defineDoc3({
|
|
779
819
|
kind: "todo.list",
|
|
780
820
|
version: 1,
|
|
781
821
|
scope: "conversation",
|
|
@@ -818,7 +858,7 @@ import { basename, join as join3, resolve } from "node:path";
|
|
|
818
858
|
import { clampThinkingLevel as clampThinkingLevel2 } from "@earendil-works/pi-ai";
|
|
819
859
|
import {
|
|
820
860
|
AgentDoc as AgentDoc2,
|
|
821
|
-
defineDoc as
|
|
861
|
+
defineDoc as defineDoc4
|
|
822
862
|
} from "@earendil-works/pi-durable";
|
|
823
863
|
|
|
824
864
|
// apps/host/src/checkout.ts
|
|
@@ -849,27 +889,6 @@ async function gitBase(projectPath) {
|
|
|
849
889
|
return;
|
|
850
890
|
return { repo, subdir: relative(repo, projectPath), base };
|
|
851
891
|
}
|
|
852
|
-
var SNAPSHOT_IDENTITY = {
|
|
853
|
-
GIT_AUTHOR_NAME: "PiNomad",
|
|
854
|
-
GIT_AUTHOR_EMAIL: "pinomad@localhost",
|
|
855
|
-
GIT_COMMITTER_NAME: "PiNomad",
|
|
856
|
-
GIT_COMMITTER_EMAIL: "pinomad@localhost"
|
|
857
|
-
};
|
|
858
|
-
async function snapshotOf(sourceDir) {
|
|
859
|
-
const indexFile = join2(tmpdir(), `pinomad-index-${randomBytes(8).toString("hex")}`);
|
|
860
|
-
const env = { GIT_INDEX_FILE: indexFile };
|
|
861
|
-
try {
|
|
862
|
-
const { stdout: head } = await git(sourceDir, ["rev-parse", "--verify", "HEAD"], env);
|
|
863
|
-
const base = head.trim();
|
|
864
|
-
await git(sourceDir, ["read-tree", base], env);
|
|
865
|
-
await git(sourceDir, ["add", "-A"], env);
|
|
866
|
-
const { stdout: tree } = await git(sourceDir, ["write-tree"], env);
|
|
867
|
-
const { stdout: commit } = await git(sourceDir, ["commit-tree", tree.trim(), "-p", base, "-m", "pinomad fork snapshot"], { ...env, ...SNAPSHOT_IDENTITY });
|
|
868
|
-
return { base, snapshot: commit.trim() };
|
|
869
|
-
} finally {
|
|
870
|
-
await rm(indexFile, { force: true });
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
892
|
var repoLocks = new Map;
|
|
874
893
|
function locked(repo, work) {
|
|
875
894
|
const next = (repoLocks.get(repo) ?? Promise.resolve()).then(work, work);
|
|
@@ -933,7 +952,7 @@ async function changesOf(dir, base, maxBytes = 1024 * 1024) {
|
|
|
933
952
|
}
|
|
934
953
|
|
|
935
954
|
// apps/host/src/organization.ts
|
|
936
|
-
var IndexDoc =
|
|
955
|
+
var IndexDoc = defineDoc4({
|
|
937
956
|
kind: "pinomad.index",
|
|
938
957
|
version: 1,
|
|
939
958
|
scope: "session",
|
|
@@ -1057,21 +1076,42 @@ async function archive(harness, id, archived, context) {
|
|
|
1057
1076
|
entry.archived = archived;
|
|
1058
1077
|
}, context);
|
|
1059
1078
|
}
|
|
1060
|
-
|
|
1079
|
+
var forkFlags = new WeakMap;
|
|
1080
|
+
async function forkFlagOf(harness, id, context) {
|
|
1081
|
+
const cached = forkFlags.get(harness)?.get(id);
|
|
1082
|
+
if (cached !== undefined)
|
|
1083
|
+
return cached;
|
|
1084
|
+
const record = await harness.commit((tx) => tx.conversation(id), context);
|
|
1085
|
+
if (record === undefined)
|
|
1086
|
+
return false;
|
|
1087
|
+
let flags = forkFlags.get(harness);
|
|
1088
|
+
if (flags === undefined)
|
|
1089
|
+
forkFlags.set(harness, flags = new Map);
|
|
1090
|
+
flags.set(id, record.parent !== undefined);
|
|
1091
|
+
return record.parent !== undefined;
|
|
1092
|
+
}
|
|
1093
|
+
async function checkoutInfo(harness, input, context) {
|
|
1061
1094
|
const agent = await input.read.snapshot(AgentDoc2, input.conversationId, context);
|
|
1062
1095
|
const cwd = agent?.cwd;
|
|
1063
1096
|
if (cwd === undefined)
|
|
1064
1097
|
return;
|
|
1065
|
-
const index = await
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1098
|
+
const [index, fork] = await Promise.all([
|
|
1099
|
+
input.read.snapshot(IndexDoc, context),
|
|
1100
|
+
forkFlagOf(harness, input.conversationId, context)
|
|
1101
|
+
]);
|
|
1102
|
+
const checkout = checkoutAt(index?.checkouts, cwd);
|
|
1069
1103
|
return {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1104
|
+
fork,
|
|
1105
|
+
...checkout === undefined ? {} : {
|
|
1106
|
+
checkout: {
|
|
1107
|
+
branch: checkout.branch,
|
|
1108
|
+
base: checkout.base,
|
|
1109
|
+
projectPath: join3(checkout.repo, checkout.subdir),
|
|
1110
|
+
repo: checkout.repo,
|
|
1111
|
+
worktreeRoot: checkout.path,
|
|
1112
|
+
own: checkout.conversationId === input.conversationId
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1075
1115
|
};
|
|
1076
1116
|
}
|
|
1077
1117
|
async function cleanupArchivedWorktrees(harness, rootId, context) {
|
|
@@ -1410,15 +1450,43 @@ import lockfile from "proper-lockfile";
|
|
|
1410
1450
|
// apps/host/src/gateway.ts
|
|
1411
1451
|
import { createServer } from "node:http";
|
|
1412
1452
|
import { networkInterfaces } from "node:os";
|
|
1413
|
-
import { join as join4,
|
|
1453
|
+
import { join as join4, sep as sep3 } from "node:path";
|
|
1414
1454
|
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
1415
1455
|
import { clampThinkingLevel as clampThinkingLevel3, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
1416
1456
|
import {
|
|
1417
|
-
AgentDoc as AgentDoc3
|
|
1457
|
+
AgentDoc as AgentDoc3,
|
|
1458
|
+
LiveDoc
|
|
1418
1459
|
} from "@earendil-works/pi-durable";
|
|
1419
1460
|
import { WebSocket as WebSocket2, WebSocketServer } from "ws";
|
|
1420
1461
|
import { Value as Value2 } from "typebox/value";
|
|
1421
1462
|
|
|
1463
|
+
// packages/protocol/src/tool-target.ts
|
|
1464
|
+
var TARGET_KEYS = [
|
|
1465
|
+
"path",
|
|
1466
|
+
"file_path",
|
|
1467
|
+
"filePath",
|
|
1468
|
+
"command",
|
|
1469
|
+
"cmd",
|
|
1470
|
+
"query",
|
|
1471
|
+
"pattern",
|
|
1472
|
+
"url",
|
|
1473
|
+
"name",
|
|
1474
|
+
"description",
|
|
1475
|
+
"task"
|
|
1476
|
+
];
|
|
1477
|
+
var TARGET_MAX_LENGTH = 120;
|
|
1478
|
+
function toolTarget(args) {
|
|
1479
|
+
if (typeof args !== "object" || args === null)
|
|
1480
|
+
return;
|
|
1481
|
+
for (const key of TARGET_KEYS) {
|
|
1482
|
+
const value = args[key];
|
|
1483
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1484
|
+
return value.length > TARGET_MAX_LENGTH ? `${value.slice(0, TARGET_MAX_LENGTH)}…` : value;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1422
1490
|
// apps/host/src/relay-link.ts
|
|
1423
1491
|
import { WebSocket } from "ws";
|
|
1424
1492
|
function relayWsBase(origin) {
|
|
@@ -1567,8 +1635,20 @@ function startRelayLink(options) {
|
|
|
1567
1635
|
|
|
1568
1636
|
// apps/host/src/gateway.ts
|
|
1569
1637
|
var context = BACKGROUND_CONTEXT;
|
|
1638
|
+
var forkTails = new WeakMap;
|
|
1639
|
+
function serializedFork(harness, work) {
|
|
1640
|
+
const next = (forkTails.get(harness) ?? Promise.resolve()).then(work, work);
|
|
1641
|
+
const settled = next.then(() => {}, () => {});
|
|
1642
|
+
forkTails.set(harness, settled);
|
|
1643
|
+
settled.then(() => {
|
|
1644
|
+
if (forkTails.get(harness) === settled)
|
|
1645
|
+
forkTails.delete(harness);
|
|
1646
|
+
});
|
|
1647
|
+
return next;
|
|
1648
|
+
}
|
|
1649
|
+
var LOOPBACK_SECURE_PATH = "/secure";
|
|
1570
1650
|
async function startGateway(options) {
|
|
1571
|
-
const conversations = await ConversationList.open(options.harness);
|
|
1651
|
+
const conversations = await ConversationList.open(options.harness, options.docs);
|
|
1572
1652
|
const http = createServer((request, response) => {
|
|
1573
1653
|
serveWebClient(request, response, options.webRoot).catch(() => {
|
|
1574
1654
|
if (!response.headersSent)
|
|
@@ -1606,22 +1686,35 @@ async function startGateway(options) {
|
|
|
1606
1686
|
},
|
|
1607
1687
|
createPairing: () => {
|
|
1608
1688
|
const spec = options.secure;
|
|
1609
|
-
if (spec === undefined
|
|
1689
|
+
if (spec === undefined)
|
|
1610
1690
|
return;
|
|
1611
1691
|
const { secret, expiresAt } = spec.offers.create();
|
|
1612
1692
|
const pair = `pair=${toBase64Url(spec.hostKey.publicKey)}.${secret}`;
|
|
1613
1693
|
if (relay !== undefined) {
|
|
1614
|
-
const
|
|
1615
|
-
return { url: `${relay.origin}/#${pair}&url=${encodeURIComponent(
|
|
1694
|
+
const target2 = `${relayWsBase(relay.origin)}/c/${relay.hostId}`;
|
|
1695
|
+
return { url: `${relay.origin}/#${pair}&url=${encodeURIComponent(target2)}`, expiresAt };
|
|
1616
1696
|
}
|
|
1617
|
-
|
|
1697
|
+
if (remote !== undefined)
|
|
1698
|
+
return { url: `${remote.advertiseUrl}/#${pair}`, expiresAt };
|
|
1699
|
+
const target = `ws://127.0.0.1:${port}${LOOPBACK_SECURE_PATH}`;
|
|
1700
|
+
return { url: `http://127.0.0.1:${port}/#${pair}&url=${encodeURIComponent(target)}`, expiresAt };
|
|
1618
1701
|
}
|
|
1619
1702
|
});
|
|
1620
1703
|
clients.add(client);
|
|
1621
1704
|
};
|
|
1622
1705
|
server.on("connection", (socket, request) => {
|
|
1623
1706
|
socket.on("error", () => socket.terminate());
|
|
1624
|
-
const
|
|
1707
|
+
const url = new URL(request.url ?? "/", "ws://127.0.0.1");
|
|
1708
|
+
if (url.pathname === LOOPBACK_SECURE_PATH) {
|
|
1709
|
+
const secure2 = options.secure;
|
|
1710
|
+
if (secure2 === undefined) {
|
|
1711
|
+
socket.close(UNAUTHORIZED_CLOSE_CODE, "unauthorized");
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
secureHandshake(socket, secure2, options.harness, register);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
const token = url.searchParams.get("token");
|
|
1625
1718
|
const origin = request.headers.origin;
|
|
1626
1719
|
if (token !== options.token || origin !== undefined && !browserOrigins.includes(origin)) {
|
|
1627
1720
|
socket.close(UNAUTHORIZED_CLOSE_CODE, "unauthorized");
|
|
@@ -1807,36 +1900,131 @@ async function startRemote(options, register) {
|
|
|
1807
1900
|
|
|
1808
1901
|
class ConversationList {
|
|
1809
1902
|
#listeners = new Set;
|
|
1810
|
-
#
|
|
1903
|
+
#questionTokens;
|
|
1904
|
+
#questionKinds;
|
|
1905
|
+
#records = new Map;
|
|
1906
|
+
#facts = new Map;
|
|
1907
|
+
#index = new Map;
|
|
1908
|
+
#list = [];
|
|
1811
1909
|
#unsubscribe = () => {};
|
|
1812
1910
|
#notifying = false;
|
|
1813
|
-
constructor(
|
|
1814
|
-
this.#
|
|
1911
|
+
constructor(docs) {
|
|
1912
|
+
this.#questionTokens = (docs ?? []).filter((doc) => doc.presentation === "pinomad.question").map((doc) => doc.token);
|
|
1913
|
+
this.#questionKinds = new Set(this.#questionTokens.map((token) => token.definition.kind));
|
|
1815
1914
|
}
|
|
1816
|
-
static async open(harness) {
|
|
1915
|
+
static async open(harness, docs) {
|
|
1916
|
+
const list = new ConversationList(docs);
|
|
1817
1917
|
const summaries = [];
|
|
1818
1918
|
let cursor;
|
|
1819
1919
|
do {
|
|
1820
1920
|
const page = await harness.commit((tx) => tx.scanConversations({}, 256, cursor), context);
|
|
1821
|
-
for (const record of page.items)
|
|
1822
|
-
|
|
1921
|
+
for (const record of page.items) {
|
|
1922
|
+
list.#records.set(record.id, record);
|
|
1923
|
+
const facts = await list.#load(harness, record);
|
|
1924
|
+
list.#facts.set(record.id, facts);
|
|
1925
|
+
list.#index.set(record.id, summaries.length);
|
|
1926
|
+
summaries.push(summaryOf(record, facts));
|
|
1927
|
+
}
|
|
1823
1928
|
cursor = page.next;
|
|
1824
1929
|
} while (cursor !== undefined);
|
|
1825
|
-
|
|
1826
|
-
list.#unsubscribe = harness.subscribeCommits((publication) =>
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1930
|
+
list.#list = summaries;
|
|
1931
|
+
list.#unsubscribe = harness.subscribeCommits((publication) => list.#observe(publication));
|
|
1932
|
+
return list;
|
|
1933
|
+
}
|
|
1934
|
+
async#load(harness, record) {
|
|
1935
|
+
const history = await historyFacts(harness, record.id);
|
|
1936
|
+
const facts = { pending: new Set, ...history };
|
|
1937
|
+
const live = await harness.snapshot(LiveDoc, record.id, context);
|
|
1938
|
+
facts.running = live?.run !== undefined;
|
|
1939
|
+
facts.tools = live?.tools;
|
|
1940
|
+
for (const token of this.#questionTokens) {
|
|
1941
|
+
const value = await harness.snapshot(token, record.id, context);
|
|
1942
|
+
if (Value2.Check(QuestionSchema, value) && value.requests.some((request) => request.resolution === undefined)) {
|
|
1943
|
+
facts.pending.add(token.definition.kind);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
if (record.owner !== undefined) {
|
|
1947
|
+
const label = (await harness.snapshot(SubagentDoc, record.id, context))?.label;
|
|
1948
|
+
if (typeof label === "string" && label !== "")
|
|
1949
|
+
facts.label = label;
|
|
1950
|
+
}
|
|
1951
|
+
return facts;
|
|
1952
|
+
}
|
|
1953
|
+
#observe(publication) {
|
|
1954
|
+
const touched = new Set;
|
|
1955
|
+
for (const change of publication.changes) {
|
|
1956
|
+
if (change.type === "conversation") {
|
|
1957
|
+
this.#records.set(change.value.id, change.value);
|
|
1958
|
+
touched.add(change.value.id);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
for (const change of publication.changes) {
|
|
1962
|
+
if (change.type === "entry") {
|
|
1963
|
+
const entry = change.value;
|
|
1964
|
+
if (entry.kind !== "pi.user" && entry.kind !== "pi.assistant")
|
|
1965
|
+
continue;
|
|
1966
|
+
const facts = this.#fact(entry.conversationId);
|
|
1967
|
+
if (entry.kind === "pi.user" && facts.title === undefined)
|
|
1968
|
+
Object.assign(facts, titleOf(entry));
|
|
1969
|
+
if (facts.newest !== undefined && entry.id <= facts.newest)
|
|
1970
|
+
continue;
|
|
1971
|
+
touched.add(entry.conversationId);
|
|
1972
|
+
facts.newest = entry.id;
|
|
1973
|
+
const message = entry.model?.[0];
|
|
1974
|
+
facts.updatedAt = typeof message?.timestamp === "number" ? message.timestamp : undefined;
|
|
1975
|
+
facts.failed = entry.kind === "pi.assistant" && message?.role === "assistant" && message.stopReason === "error";
|
|
1976
|
+
facts.calls = entry.kind === "pi.assistant" ? callsOf(message) : undefined;
|
|
1977
|
+
} else if (change.type === "document") {
|
|
1978
|
+
const id = change.conversationId;
|
|
1979
|
+
if (id === undefined)
|
|
1980
|
+
continue;
|
|
1981
|
+
const facts = this.#fact(id);
|
|
1982
|
+
if (change.record.kind === "pi.live") {
|
|
1983
|
+
const live = change.value;
|
|
1984
|
+
facts.running = live?.run !== undefined;
|
|
1985
|
+
facts.tools = live?.tools;
|
|
1986
|
+
touched.add(id);
|
|
1987
|
+
} else if (this.#questionKinds.has(change.record.kind)) {
|
|
1988
|
+
const value = change.value;
|
|
1989
|
+
const pending = Value2.Check(QuestionSchema, value) && value.requests.some((request) => request.resolution === undefined);
|
|
1990
|
+
const next2 = new Set(facts.pending);
|
|
1991
|
+
if (pending)
|
|
1992
|
+
next2.add(change.record.kind);
|
|
1993
|
+
else
|
|
1994
|
+
next2.delete(change.record.kind);
|
|
1995
|
+
facts.pending = next2;
|
|
1996
|
+
touched.add(id);
|
|
1997
|
+
} else if (change.record.kind === SubagentDoc.definition.kind) {
|
|
1998
|
+
const label = change.value?.label;
|
|
1999
|
+
facts.label = typeof label === "string" && label !== "" ? label : undefined;
|
|
2000
|
+
touched.add(id);
|
|
1834
2001
|
}
|
|
1835
2002
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2003
|
+
}
|
|
2004
|
+
let next;
|
|
2005
|
+
for (const id of touched) {
|
|
2006
|
+
const record = this.#records.get(id);
|
|
2007
|
+
if (record === undefined)
|
|
2008
|
+
continue;
|
|
2009
|
+
const summary = summaryOf(record, this.#fact(id));
|
|
2010
|
+
const index = this.#index.get(id);
|
|
2011
|
+
if (index === undefined) {
|
|
2012
|
+
next ??= this.#list.slice();
|
|
2013
|
+
this.#index.set(id, next.length);
|
|
2014
|
+
next.push(summary);
|
|
2015
|
+
} else if (!sameSummary(this.#list[index], summary)) {
|
|
2016
|
+
next ??= this.#list.slice();
|
|
2017
|
+
next[index] = summary;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
if (next !== undefined)
|
|
2021
|
+
this.#set(next);
|
|
2022
|
+
}
|
|
2023
|
+
#fact(id) {
|
|
2024
|
+
let facts = this.#facts.get(id);
|
|
2025
|
+
if (facts === undefined)
|
|
2026
|
+
this.#facts.set(id, facts = { pending: new Set });
|
|
2027
|
+
return facts;
|
|
1840
2028
|
}
|
|
1841
2029
|
get value() {
|
|
1842
2030
|
return this.#list;
|
|
@@ -1861,14 +2049,29 @@ class ConversationList {
|
|
|
1861
2049
|
this.#listeners.clear();
|
|
1862
2050
|
}
|
|
1863
2051
|
}
|
|
1864
|
-
function summaryOf(record) {
|
|
2052
|
+
function summaryOf(record, facts) {
|
|
1865
2053
|
const parent = record.parent?.conversationId ?? record.owner?.conversationId;
|
|
2054
|
+
const status = facts.pending.size > 0 ? "needs-answer" : facts.running === true ? "running" : facts.failed === true ? "failed" : undefined;
|
|
2055
|
+
const runningSlot = facts.running === true ? facts.tools?.find((slot) => slot.status === "running") : undefined;
|
|
2056
|
+
const activity = runningSlot === undefined ? undefined : {
|
|
2057
|
+
tool: runningSlot.name,
|
|
2058
|
+
...facts.calls?.get(runningSlot.callId) === undefined ? {} : { target: facts.calls.get(runningSlot.callId) }
|
|
2059
|
+
};
|
|
1866
2060
|
return {
|
|
1867
2061
|
id: record.id,
|
|
1868
2062
|
kind: record.owner !== undefined ? "subagent" : record.parent !== undefined ? "fork" : "conversation",
|
|
1869
|
-
...parent === undefined ? {} : { parent }
|
|
2063
|
+
...parent === undefined ? {} : { parent },
|
|
2064
|
+
...record.parent === undefined ? {} : { forkedAt: String(record.parent.at) },
|
|
2065
|
+
...facts.title === undefined ? {} : { title: facts.title },
|
|
2066
|
+
...status === undefined ? {} : { status },
|
|
2067
|
+
...facts.updatedAt === undefined ? {} : { updatedAt: facts.updatedAt },
|
|
2068
|
+
...facts.label === undefined ? {} : { label: facts.label },
|
|
2069
|
+
...activity === undefined ? {} : { activity }
|
|
1870
2070
|
};
|
|
1871
2071
|
}
|
|
2072
|
+
function sameSummary(a, b) {
|
|
2073
|
+
return a.id === b.id && a.kind === b.kind && a.parent === b.parent && a.forkedAt === b.forkedAt && a.title === b.title && a.status === b.status && a.updatedAt === b.updatedAt && a.label === b.label && a.activity?.tool === b.activity?.tool && a.activity?.target === b.activity?.target;
|
|
2074
|
+
}
|
|
1872
2075
|
function titleOf(entry) {
|
|
1873
2076
|
const message = entry?.model?.[0];
|
|
1874
2077
|
if (message?.role !== "user")
|
|
@@ -1876,16 +2079,40 @@ function titleOf(entry) {
|
|
|
1876
2079
|
const text = typeof message.content === "string" ? message.content : message.content.flatMap((block) => block.type === "text" ? [block.text] : []).join(" ");
|
|
1877
2080
|
return { title: text.replace(/\s+/g, " ").trim() };
|
|
1878
2081
|
}
|
|
1879
|
-
async function
|
|
2082
|
+
async function historyFacts(harness, id) {
|
|
1880
2083
|
const conversation = await harness.conversation(id, context);
|
|
1881
2084
|
let first;
|
|
2085
|
+
let newest;
|
|
1882
2086
|
let cursor;
|
|
1883
2087
|
do {
|
|
1884
2088
|
const page = await conversation.entries({}, 256, cursor, context);
|
|
1885
2089
|
first = page.items.findLast((entry) => entry.kind === "pi.user" && entry.conversationId === id) ?? first;
|
|
2090
|
+
newest ??= page.items.find((entry) => entry.conversationId === id && (entry.kind === "pi.user" || entry.kind === "pi.assistant"));
|
|
1886
2091
|
cursor = page.next;
|
|
1887
2092
|
} while (cursor !== undefined);
|
|
1888
|
-
|
|
2093
|
+
const message = newest?.model?.[0];
|
|
2094
|
+
return {
|
|
2095
|
+
...titleOf(first),
|
|
2096
|
+
...newest === undefined ? {} : {
|
|
2097
|
+
newest: newest.id,
|
|
2098
|
+
...typeof message?.timestamp === "number" ? { updatedAt: message.timestamp } : {},
|
|
2099
|
+
failed: newest.kind === "pi.assistant" && message?.role === "assistant" && message.stopReason === "error",
|
|
2100
|
+
...newest.kind === "pi.assistant" ? { calls: callsOf(message) } : {}
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
function callsOf(message) {
|
|
2105
|
+
if (message?.role !== "assistant")
|
|
2106
|
+
return;
|
|
2107
|
+
const content = message.content;
|
|
2108
|
+
if (!Array.isArray(content))
|
|
2109
|
+
return;
|
|
2110
|
+
const calls = new Map;
|
|
2111
|
+
for (const block of content) {
|
|
2112
|
+
if (block?.type === "toolCall" && typeof block.id === "string")
|
|
2113
|
+
calls.set(block.id, toolTarget(block.arguments));
|
|
2114
|
+
}
|
|
2115
|
+
return calls.size === 0 ? undefined : calls;
|
|
1889
2116
|
}
|
|
1890
2117
|
|
|
1891
2118
|
class GatewayClient {
|
|
@@ -1904,24 +2131,38 @@ class GatewayClient {
|
|
|
1904
2131
|
this.devicePublicKey = hooks.devicePublicKey;
|
|
1905
2132
|
connection.handle({
|
|
1906
2133
|
message: (text) => {
|
|
1907
|
-
let
|
|
2134
|
+
let parsed;
|
|
1908
2135
|
try {
|
|
1909
|
-
|
|
2136
|
+
parsed = parseClientFrame(JSON.parse(text));
|
|
1910
2137
|
} catch {
|
|
1911
|
-
connection.close(
|
|
2138
|
+
connection.close(INVALID_FRAME_CLOSE_CODE, "Invalid client frame");
|
|
1912
2139
|
return;
|
|
1913
2140
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2141
|
+
switch (parsed.kind) {
|
|
2142
|
+
case "frame":
|
|
2143
|
+
this.#receive(parsed.frame).catch(() => connection.close(1011, "Gateway request failed"));
|
|
2144
|
+
return;
|
|
2145
|
+
case "unsupportedCall":
|
|
2146
|
+
this.send({ type: "result", id: parsed.id, ok: false, error: `Unsupported method: ${parsed.method}` });
|
|
2147
|
+
return;
|
|
2148
|
+
case "unsupportedStream":
|
|
2149
|
+
if (parsed.type === "subscribe") {
|
|
2150
|
+
this.send({ type: "ended", stream: parsed.stream, reason: "Unsupported stream" });
|
|
2151
|
+
}
|
|
2152
|
+
return;
|
|
2153
|
+
case "unknownType":
|
|
2154
|
+
return;
|
|
2155
|
+
case "malformed":
|
|
2156
|
+
connection.close(INVALID_FRAME_CLOSE_CODE, "Invalid client frame");
|
|
2157
|
+
return;
|
|
1917
2158
|
}
|
|
1918
|
-
this.#receive(frame).catch(() => connection.close(1011, "Gateway request failed"));
|
|
1919
2159
|
},
|
|
1920
2160
|
closed: () => hooks.closed(this)
|
|
1921
2161
|
});
|
|
1922
2162
|
this.send({
|
|
1923
2163
|
type: "hello",
|
|
1924
|
-
protocol:
|
|
2164
|
+
protocol: PROTOCOL,
|
|
2165
|
+
...packagedVersion === undefined ? {} : { hostVersion: packagedVersion },
|
|
1925
2166
|
session: options.session,
|
|
1926
2167
|
models: options.modelSummaries(),
|
|
1927
2168
|
defaults: options.defaults,
|
|
@@ -2175,15 +2416,34 @@ class GatewayClient {
|
|
|
2175
2416
|
}
|
|
2176
2417
|
case "fork": {
|
|
2177
2418
|
const { entryId, removeTools = [] } = args;
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2419
|
+
return await serializedFork(this.#options.harness, async () => {
|
|
2420
|
+
const at = Number(entryId);
|
|
2421
|
+
await this.#options.harness.commit(async (tx) => {
|
|
2422
|
+
const record = await tx.conversation(conversation.id);
|
|
2423
|
+
if (record === undefined)
|
|
2424
|
+
throw new Error(`Conversation ${conversation.id} does not exist`);
|
|
2425
|
+
if (record.parent !== undefined || record.owner !== undefined) {
|
|
2426
|
+
throw new Error("Only top-level conversations can be forked");
|
|
2427
|
+
}
|
|
2428
|
+
let cursor;
|
|
2429
|
+
do {
|
|
2430
|
+
const page = await tx.scanConversations({}, 256, cursor);
|
|
2431
|
+
for (const candidate of page.items) {
|
|
2432
|
+
if (candidate.parent?.conversationId === conversation.id && candidate.parent.at === at) {
|
|
2433
|
+
throw new Error("This message already has a fork");
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
cursor = page.next;
|
|
2437
|
+
} while (cursor !== undefined);
|
|
2438
|
+
}, context);
|
|
2439
|
+
const agent = await conversation.agent(context);
|
|
2440
|
+
const remove = agent.tools.filter((tool) => removeTools.includes(tool.name));
|
|
2441
|
+
const fork = await conversation.fork(at, {
|
|
2442
|
+
ownership: { kind: "ownerless" },
|
|
2443
|
+
...remove.length === 0 ? {} : { agent: { tools: { remove } } }
|
|
2444
|
+
}, context);
|
|
2445
|
+
return { conversationId: fork.id };
|
|
2446
|
+
});
|
|
2187
2447
|
}
|
|
2188
2448
|
case "answer": {
|
|
2189
2449
|
const { kind, requestId, answers } = args;
|
|
@@ -2267,41 +2527,6 @@ class GatewayClient {
|
|
|
2267
2527
|
return { available: false, reason: "Not in a git repository" };
|
|
2268
2528
|
return { available: true, ...await changesOf(base.repo, base.base) };
|
|
2269
2529
|
}
|
|
2270
|
-
async#forkCheckout(cwd) {
|
|
2271
|
-
if (cwd === undefined)
|
|
2272
|
-
return;
|
|
2273
|
-
const index = await this.#options.harness.snapshot(IndexDoc, context);
|
|
2274
|
-
const existing = checkoutAt(index?.checkouts, cwd);
|
|
2275
|
-
let source;
|
|
2276
|
-
if (existing !== undefined) {
|
|
2277
|
-
source = { repo: existing.repo, dir: existing.path, subdir: relative2(existing.path, cwd) };
|
|
2278
|
-
} else {
|
|
2279
|
-
if (cwd.startsWith(this.#chatsPrefix))
|
|
2280
|
-
return;
|
|
2281
|
-
const base2 = await gitBase(cwd);
|
|
2282
|
-
if (base2 === undefined)
|
|
2283
|
-
return;
|
|
2284
|
-
source = { repo: base2.repo, dir: base2.repo, subdir: base2.subdir };
|
|
2285
|
-
}
|
|
2286
|
-
const { base, snapshot } = await snapshotOf(source.dir);
|
|
2287
|
-
const suffix = branchSuffix();
|
|
2288
|
-
return async (tx, id) => {
|
|
2289
|
-
const path = worktreePath(this.#options.dataDir, id);
|
|
2290
|
-
const doc = await tx.doc(IndexDoc);
|
|
2291
|
-
if (doc.checkouts === undefined)
|
|
2292
|
-
doc.checkouts = [];
|
|
2293
|
-
doc.checkouts.push({
|
|
2294
|
-
conversationId: id,
|
|
2295
|
-
path,
|
|
2296
|
-
repo: source.repo,
|
|
2297
|
-
subdir: source.subdir,
|
|
2298
|
-
branch: worktreeBranch(id, suffix),
|
|
2299
|
-
base,
|
|
2300
|
-
snapshot
|
|
2301
|
-
});
|
|
2302
|
-
(await tx.doc(AgentDoc3, id)).cwd = join4(path, source.subdir);
|
|
2303
|
-
};
|
|
2304
|
-
}
|
|
2305
2530
|
async#agent(id) {
|
|
2306
2531
|
return await this.#options.harness.snapshot(AgentDoc3, id, context) ?? {};
|
|
2307
2532
|
}
|
|
@@ -2346,7 +2571,14 @@ async function openHost(options) {
|
|
|
2346
2571
|
const reports = [];
|
|
2347
2572
|
let report = (error) => void reports.push(error);
|
|
2348
2573
|
const registry = createRegistry();
|
|
2349
|
-
const extensions = typeof options.extensions === "function" ? options.extensions({
|
|
2574
|
+
const extensions = typeof options.extensions === "function" ? options.extensions({
|
|
2575
|
+
scriptTools: () => mcp?.scriptTools() ?? [],
|
|
2576
|
+
harness: () => {
|
|
2577
|
+
if (harness === undefined)
|
|
2578
|
+
throw new Error("harness is not open yet");
|
|
2579
|
+
return harness;
|
|
2580
|
+
}
|
|
2581
|
+
}) : options.extensions ?? [];
|
|
2350
2582
|
for (const { extension } of extensions)
|
|
2351
2583
|
registry.install(extension);
|
|
2352
2584
|
mcp = options.mcpConfig === undefined ? undefined : startMcp({ configPath: options.mcpConfig, registry });
|
|
@@ -2385,9 +2617,8 @@ async function openHost(options) {
|
|
|
2385
2617
|
await ensureDevices(harness, context2);
|
|
2386
2618
|
for (const path of options.projects ?? [])
|
|
2387
2619
|
await addProject(harness, path, context2);
|
|
2388
|
-
const
|
|
2389
|
-
const
|
|
2390
|
-
const offers = remoteAccess ? pairingOffers(options.remote?.pairingTtlMs) : undefined;
|
|
2620
|
+
const hostKey = await loadHostKey(options.dataDir);
|
|
2621
|
+
const offers = pairingOffers(options.remote?.pairingTtlMs);
|
|
2391
2622
|
const relayKey = options.relay === undefined ? undefined : await loadRelayKey(options.dataDir);
|
|
2392
2623
|
const defaults = initialModel === undefined ? {} : {
|
|
2393
2624
|
model: { provider: initialModel.provider, modelId: initialModel.modelId },
|
|
@@ -2407,24 +2638,22 @@ async function openHost(options) {
|
|
|
2407
2638
|
toolPresentations: Object.assign({}, ...extensions.map((extension) => extension.tools ?? {})),
|
|
2408
2639
|
...mcp === undefined ? {} : { mcp: mcp.status },
|
|
2409
2640
|
...options.webRoot === undefined ? {} : { webRoot: options.webRoot },
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
...options.relay.pingIntervalMs === undefined ? {} : { pingIntervalMs: options.relay.pingIntervalMs }
|
|
2427
|
-
}
|
|
2641
|
+
secure: {
|
|
2642
|
+
hostKey,
|
|
2643
|
+
offers,
|
|
2644
|
+
...options.remote?.handshakeTimeoutMs === undefined ? {} : { handshakeTimeoutMs: options.remote.handshakeTimeoutMs },
|
|
2645
|
+
...options.remote === undefined ? {} : {
|
|
2646
|
+
lan: {
|
|
2647
|
+
port: options.remote.port,
|
|
2648
|
+
...options.remote.publicUrl === undefined ? {} : { publicUrl: options.remote.publicUrl }
|
|
2649
|
+
}
|
|
2650
|
+
},
|
|
2651
|
+
...options.relay === undefined || relayKey === undefined ? {} : {
|
|
2652
|
+
relay: {
|
|
2653
|
+
origin: options.relay.origin,
|
|
2654
|
+
signingKey: relayKey,
|
|
2655
|
+
...options.relay.reconnectDelayMs === undefined ? {} : { reconnectDelayMs: options.relay.reconnectDelayMs },
|
|
2656
|
+
...options.relay.pingIntervalMs === undefined ? {} : { pingIntervalMs: options.relay.pingIntervalMs }
|
|
2428
2657
|
}
|
|
2429
2658
|
}
|
|
2430
2659
|
}
|
|
@@ -2597,7 +2826,10 @@ async function main(argv) {
|
|
|
2597
2826
|
const dataDir = resolve3(values["data-dir"] ?? DEFAULT_DATA_DIR);
|
|
2598
2827
|
const extensionsFor = (models, summaries) => (host2) => {
|
|
2599
2828
|
const others = [
|
|
2600
|
-
createContext({
|
|
2829
|
+
createContext({
|
|
2830
|
+
agentsHome: join6(homedir2(), ".agents"),
|
|
2831
|
+
checkout: (input, context3) => checkoutInfo(host2.harness(), input, context3)
|
|
2832
|
+
}),
|
|
2601
2833
|
coding,
|
|
2602
2834
|
todo,
|
|
2603
2835
|
question,
|