toyon 0.8.0 → 0.9.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/bridge.js +1 -1
- package/dist/cli.js +19 -1
- package/dist/daemon.js +1323 -426
- package/dist/shell/assets/{Editor-CFH2QbSg.js → Editor-DAJni9OF.js} +137 -137
- package/dist/shell/assets/{XTerm-C3aMkE_w.js → XTerm-dug6M6yd.js} +16 -16
- package/dist/shell/assets/{cssMode-dc6AdHsZ.js → cssMode-B5iiwdXn.js} +1 -1
- package/dist/shell/assets/{freemarker2-CxtmNsLB.js → freemarker2-CdVcBNE9.js} +1 -1
- package/dist/shell/assets/{grammar-B-X0FPRk.js → grammar-Cfxx1yRn.js} +5 -5
- package/dist/shell/assets/{handlebars-DVzmQ0KM.js → handlebars-CW5EFZC2.js} +1 -1
- package/dist/shell/assets/{html-CqqXL9ke.js → html-B8gP1xu-.js} +1 -1
- package/dist/shell/assets/{htmlMode-Bg-Em0rL.js → htmlMode-C6I99y8u.js} +1 -1
- package/dist/shell/assets/index-BRSL9cyD.css +1 -0
- package/dist/shell/assets/index-D_PsyNmp.js +266 -0
- package/dist/shell/assets/{javascript-W-C4G7BB.js → javascript-C_3_LFln.js} +1 -1
- package/dist/shell/assets/{jsonMode-De4ajMpV.js → jsonMode-CD5xJEUk.js} +1 -1
- package/dist/shell/assets/{liquid-BSUg_7LQ.js → liquid-CJw93Zxn.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-BImXYajo.js → lspLanguageFeatures-wMR368iN.js} +1 -1
- package/dist/shell/assets/{mdx-B9A7RorP.js → mdx-BJ6yMgc3.js} +1 -1
- package/dist/shell/assets/{python-BD9sq3DF.js → python-BWGkxFUr.js} +1 -1
- package/dist/shell/assets/{razor-DGQVRh5-.js → razor-BV9pdU1A.js} +1 -1
- package/dist/shell/assets/{tsMode-DudQlgN_.js → tsMode-CN8OfYAX.js} +1 -1
- package/dist/shell/assets/{xml-BI6-GyBn.js → xml-DBGLqvw7.js} +1 -1
- package/dist/shell/assets/{yaml-BEUvyYtP.js → yaml-dH0C6Dth.js} +1 -1
- package/dist/shell/index.html +21 -2
- package/package.json +1 -1
- package/dist/shell/assets/index-BskWflhx.css +0 -1
- package/dist/shell/assets/index-C8QP4sgP.js +0 -255
package/dist/daemon.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../daemon/src/index.ts
|
|
3
|
-
import { rmSync as
|
|
3
|
+
import { rmSync as rmSync8, writeFileSync as writeFileSync10 } from "fs";
|
|
4
4
|
import { dirname as dirname12 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
|
|
@@ -120,8 +120,9 @@ function archiveReason(wt, f) {
|
|
|
120
120
|
if (unitsAhead(wt, f.rows) < ARCHIVE_KEEP_RECENT)
|
|
121
121
|
return null;
|
|
122
122
|
const what = lost ? "a sibling landed" : wt.landed ? "landed" : "no changes";
|
|
123
|
-
return `${what},
|
|
123
|
+
return `${what}, ${UNOPENED}${duration(f.afterMs)}`;
|
|
124
124
|
}
|
|
125
|
+
var UNOPENED = "not opened in ";
|
|
125
126
|
function nothingToKeep(w, f, lost) {
|
|
126
127
|
if (w.kind !== "worktree" || !hasOwnBranch(w))
|
|
127
128
|
return false;
|
|
@@ -157,22 +158,30 @@ function duration(ms) {
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
// ../shared/src/attachment.ts
|
|
160
|
-
var ATTACHMENT_KINDS = ["image", "paste", "pick"];
|
|
161
|
-
var ATTACHMENT_LIMITS = { image: 6, paste: 4, pick: 8 };
|
|
161
|
+
var ATTACHMENT_KINDS = ["image", "paste", "file", "pick"];
|
|
162
|
+
var ATTACHMENT_LIMITS = { image: 6, paste: 4, file: 8, pick: 8 };
|
|
162
163
|
var ATTACHMENTS_PER_MESSAGE = ATTACHMENT_KINDS.reduce((sum, k) => sum + ATTACHMENT_LIMITS[k], 0);
|
|
163
|
-
var KIND_NOUN = {
|
|
164
|
+
var KIND_NOUN = {
|
|
165
|
+
image: "image",
|
|
166
|
+
paste: "paste",
|
|
167
|
+
file: "file",
|
|
168
|
+
pick: "element"
|
|
169
|
+
};
|
|
164
170
|
var KIND_LABEL = { image: "Image", paste: "Pasted text" };
|
|
165
171
|
var attachmentLabel = (kind, n) => `${KIND_LABEL[kind]} ${n}`;
|
|
166
172
|
var limitMessage = (kind) => `at most ${ATTACHMENT_LIMITS[kind]} ${KIND_NOUN[kind]}s per message`;
|
|
167
173
|
var countOf = (list, kind) => list.filter((a) => a.kind === kind).length;
|
|
168
174
|
var overLimit = (list) => ATTACHMENT_KINDS.find((k) => countOf(list, k) > ATTACHMENT_LIMITS[k]) ?? null;
|
|
169
175
|
function nextNumbers(sent) {
|
|
170
|
-
const next = { image: 1, paste: 1, pick: 1 };
|
|
176
|
+
const next = { image: 1, paste: 1, file: 1, pick: 1 };
|
|
171
177
|
for (const list of sent)
|
|
172
178
|
for (const a of list ?? [])
|
|
173
179
|
next[a.kind] = Math.max(next[a.kind], a.n + 1);
|
|
174
180
|
return next;
|
|
175
181
|
}
|
|
182
|
+
function fmtBytes(n) {
|
|
183
|
+
return n >= 1024 * 1024 ? `${(n / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(n / 1024))} KB`;
|
|
184
|
+
}
|
|
176
185
|
|
|
177
186
|
// ../shared/src/cache.ts
|
|
178
187
|
function cachePolicy(config) {
|
|
@@ -6469,12 +6478,15 @@ var PAIR_TTL_MS = 120000;
|
|
|
6469
6478
|
var pairLink = (host, code) => `https://${host}/#pair=${code}`;
|
|
6470
6479
|
|
|
6471
6480
|
// ../shared/src/protocol/limits.ts
|
|
6472
|
-
var PROTOCOL_VERSION =
|
|
6481
|
+
var PROTOCOL_VERSION = 85;
|
|
6473
6482
|
var FILE_MAX_CHARS = 5000000;
|
|
6474
6483
|
var IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
6475
6484
|
var IMAGE_MAX_EDGE = 2576;
|
|
6476
6485
|
var IMAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
6477
6486
|
var PASTE_MAX_CHARS = 1e5;
|
|
6487
|
+
var FILE_INLINE_CHARS = 20000;
|
|
6488
|
+
var UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
6489
|
+
var VARIANTS_MAX = 3;
|
|
6478
6490
|
|
|
6479
6491
|
// ../shared/src/paste.ts
|
|
6480
6492
|
var PREVIEW_CHARS = 80;
|
|
@@ -6559,11 +6571,13 @@ var termInput = string2().max(65536);
|
|
|
6559
6571
|
var streamName = string2().min(1).max(100);
|
|
6560
6572
|
var routePath = string2().min(1).max(2000);
|
|
6561
6573
|
var pageTitle = string2().max(1000);
|
|
6574
|
+
var uploadId = string2().regex(/^[A-Za-z0-9_-]{1,64}$/);
|
|
6562
6575
|
var imageInputSchema = object({
|
|
6563
6576
|
kind: literal("image"),
|
|
6564
6577
|
name: string2().max(200),
|
|
6565
6578
|
mimeType: _enum(IMAGE_MIME_TYPES),
|
|
6566
|
-
|
|
6579
|
+
upload: uploadId,
|
|
6580
|
+
bytes: number2().int().min(1).max(IMAGE_MAX_BYTES),
|
|
6567
6581
|
width: number2().int().min(1).max(IMAGE_MAX_EDGE),
|
|
6568
6582
|
height: number2().int().min(1).max(IMAGE_MAX_EDGE)
|
|
6569
6583
|
});
|
|
@@ -6579,6 +6593,13 @@ var pasteInputSchema = object({
|
|
|
6579
6593
|
name: string2().max(200).optional(),
|
|
6580
6594
|
source: pasteSourceSchema.optional()
|
|
6581
6595
|
});
|
|
6596
|
+
var fileInputSchema = object({
|
|
6597
|
+
kind: literal("file"),
|
|
6598
|
+
upload: uploadId,
|
|
6599
|
+
name: string2().min(1).max(200),
|
|
6600
|
+
bytes: number2().int().min(1).max(UPLOAD_MAX_BYTES),
|
|
6601
|
+
text: boolean2()
|
|
6602
|
+
});
|
|
6582
6603
|
var pickInputSchema = pickMetaSchema.extend({
|
|
6583
6604
|
kind: literal("pick"),
|
|
6584
6605
|
text: string2().max(120),
|
|
@@ -6587,13 +6608,16 @@ var pickInputSchema = pickMetaSchema.extend({
|
|
|
6587
6608
|
var attachmentInputSchema = discriminatedUnion("kind", [
|
|
6588
6609
|
imageInputSchema,
|
|
6589
6610
|
pasteInputSchema,
|
|
6611
|
+
fileInputSchema,
|
|
6590
6612
|
pickInputSchema
|
|
6591
6613
|
]);
|
|
6592
|
-
var
|
|
6614
|
+
var attachmentList = array(attachmentInputSchema).max(ATTACHMENTS_PER_MESSAGE).superRefine((list, ctx) => {
|
|
6593
6615
|
const over = overLimit(list);
|
|
6594
6616
|
if (over)
|
|
6595
6617
|
ctx.addIssue({ code: "custom", message: limitMessage(over) });
|
|
6596
|
-
})
|
|
6618
|
+
});
|
|
6619
|
+
var attachments = attachmentList.optional();
|
|
6620
|
+
var boxId = id.optional();
|
|
6597
6621
|
var askAnswerSchema = object({
|
|
6598
6622
|
selected: array(string2().max(2000)).max(32),
|
|
6599
6623
|
note: string2().max(1e4).optional()
|
|
@@ -6702,7 +6726,6 @@ var themePrefsSchema = object({
|
|
|
6702
6726
|
light: string2(),
|
|
6703
6727
|
dark: string2()
|
|
6704
6728
|
});
|
|
6705
|
-
var variantSchema = object({ group: string2(), index: number2().int().min(1), of: number2().int().min(1) });
|
|
6706
6729
|
var clientMsgSchema = discriminatedUnion("t", [
|
|
6707
6730
|
object({ t: literal("subscribe"), worktreeId: id }),
|
|
6708
6731
|
object({ t: literal("unsubscribe"), worktreeId: id }),
|
|
@@ -6713,7 +6736,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6713
6736
|
clientId: string2().max(64).optional(),
|
|
6714
6737
|
text: prose,
|
|
6715
6738
|
context: ambient.optional(),
|
|
6716
|
-
attachments
|
|
6739
|
+
attachments,
|
|
6740
|
+
boxId
|
|
6717
6741
|
}),
|
|
6718
6742
|
object({
|
|
6719
6743
|
t: literal("create-worktree"),
|
|
@@ -6721,7 +6745,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6721
6745
|
repoId: id,
|
|
6722
6746
|
worktreeId: id.optional(),
|
|
6723
6747
|
prompt,
|
|
6724
|
-
|
|
6748
|
+
variants: number2().int().min(1).max(VARIANTS_MAX).optional(),
|
|
6725
6749
|
context: ambient.optional(),
|
|
6726
6750
|
attachments,
|
|
6727
6751
|
agent: id.optional(),
|
|
@@ -6729,7 +6753,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6729
6753
|
mode: permissionModeSchema.optional(),
|
|
6730
6754
|
model: string2().max(200).optional(),
|
|
6731
6755
|
effort: string2().max(100).optional(),
|
|
6732
|
-
carry: boolean2().optional()
|
|
6756
|
+
carry: boolean2().optional(),
|
|
6757
|
+
boxId
|
|
6733
6758
|
}),
|
|
6734
6759
|
object({
|
|
6735
6760
|
t: literal("batch-worktrees"),
|
|
@@ -6737,7 +6762,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6737
6762
|
prompt,
|
|
6738
6763
|
agent: id.optional(),
|
|
6739
6764
|
model: string2().max(200).optional(),
|
|
6740
|
-
effort: string2().max(100).optional()
|
|
6765
|
+
effort: string2().max(100).optional(),
|
|
6766
|
+
boxId
|
|
6741
6767
|
}),
|
|
6742
6768
|
object({ t: literal("set-worktree-mode"), worktreeId: id, mode: permissionModeSchema }),
|
|
6743
6769
|
object({ t: literal("set-worktree-model"), worktreeId: id, model: string2().max(200) }),
|
|
@@ -6751,7 +6777,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6751
6777
|
t: literal("restore-worktree"),
|
|
6752
6778
|
archiveId: id,
|
|
6753
6779
|
clientId: string2().max(64).optional(),
|
|
6754
|
-
message: object({ text: prose, attachments }).optional()
|
|
6780
|
+
message: object({ text: prose, attachments }).optional(),
|
|
6781
|
+
boxId
|
|
6755
6782
|
}),
|
|
6756
6783
|
object({ t: literal("delete-archived"), archiveId: id }),
|
|
6757
6784
|
object({
|
|
@@ -6760,7 +6787,14 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6760
6787
|
worktreeId: id
|
|
6761
6788
|
}),
|
|
6762
6789
|
object({ t: literal("git-status"), worktreeId: id }),
|
|
6763
|
-
object({
|
|
6790
|
+
object({
|
|
6791
|
+
t: literal("read-file"),
|
|
6792
|
+
worktreeId: id,
|
|
6793
|
+
path: relPath,
|
|
6794
|
+
ref: sha.optional(),
|
|
6795
|
+
since: sha.optional(),
|
|
6796
|
+
seq
|
|
6797
|
+
}),
|
|
6764
6798
|
object({ t: literal("blame-file"), worktreeId: id, path: relPath, ref: sha.optional(), seq }),
|
|
6765
6799
|
object({ t: literal("git-log"), worktreeId: id }),
|
|
6766
6800
|
object({ t: literal("git-commit"), worktreeId: id, sha }),
|
|
@@ -6801,12 +6835,24 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6801
6835
|
object({ t: literal("seen"), worktreeId: id }),
|
|
6802
6836
|
object({ t: literal("mark-unread"), worktreeId: id }),
|
|
6803
6837
|
object({ t: literal("set-draft"), boxId: id, text: prose, clientId: string2().max(64).optional() }),
|
|
6838
|
+
object({
|
|
6839
|
+
t: literal("set-attachments"),
|
|
6840
|
+
boxId: id,
|
|
6841
|
+
items: attachmentList,
|
|
6842
|
+
clientId: string2().max(64).optional()
|
|
6843
|
+
}),
|
|
6804
6844
|
object({ t: literal("refresh-git"), repoId: id }),
|
|
6805
6845
|
object({ t: literal("visit"), worktreeId: id, path: routePath, title: pageTitle.optional() }),
|
|
6806
6846
|
object({ t: literal("page-title"), worktreeId: id, path: routePath, title: pageTitle }),
|
|
6807
6847
|
object({ t: literal("forget-visit"), repoId: id, path: routePath }),
|
|
6808
6848
|
object({ t: literal("pick-variant"), worktreeId: id }),
|
|
6809
|
-
object({
|
|
6849
|
+
object({
|
|
6850
|
+
t: literal("unqueue"),
|
|
6851
|
+
worktreeId: id,
|
|
6852
|
+
index: number2().int().min(0),
|
|
6853
|
+
edit: boolean2().optional()
|
|
6854
|
+
}),
|
|
6855
|
+
object({ t: literal("redraft"), worktreeId: id, seq: number2().int().min(0) }),
|
|
6810
6856
|
object({ t: literal("changed-ranges"), worktreeId: id, path: relPath }),
|
|
6811
6857
|
object({ t: literal("rename-worktree"), worktreeId: id, title: string2().min(1).max(200) }),
|
|
6812
6858
|
object({
|
|
@@ -6870,6 +6916,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6870
6916
|
object({ t: literal("exec"), worktreeId: id, command: shellCommand.min(1) }),
|
|
6871
6917
|
object({ t: literal("exec-stop"), worktreeId: id, toolId: id }),
|
|
6872
6918
|
object({ t: literal("run-stop"), worktreeId: id, kind: _enum(["setup", "check", "commit"]) }),
|
|
6919
|
+
object({ t: literal("preview-start"), worktreeId: id }),
|
|
6873
6920
|
object({ t: literal("search-refs"), repoId: id, query: string2().max(200) }),
|
|
6874
6921
|
object({ t: literal("search-chats"), repoId: id, query: string2().max(200) }),
|
|
6875
6922
|
object({
|
|
@@ -7882,15 +7929,6 @@ function installCommand(method, version) {
|
|
|
7882
7929
|
return ["bun", "add", "-g", `toyon@${version}`];
|
|
7883
7930
|
return null;
|
|
7884
7931
|
}
|
|
7885
|
-
function registryHost(registry) {
|
|
7886
|
-
if (registry === null || registry === "")
|
|
7887
|
-
return "the npm registry";
|
|
7888
|
-
try {
|
|
7889
|
-
return new URL(registry).host;
|
|
7890
|
-
} catch {
|
|
7891
|
-
return registry;
|
|
7892
|
-
}
|
|
7893
|
-
}
|
|
7894
7932
|
function newer(a, b) {
|
|
7895
7933
|
const pa = parseVersion(a);
|
|
7896
7934
|
const pb = parseVersion(b);
|
|
@@ -8163,7 +8201,7 @@ function safeUser() {
|
|
|
8163
8201
|
// ../daemon/package.json
|
|
8164
8202
|
var package_default = {
|
|
8165
8203
|
name: "@toyon/daemon",
|
|
8166
|
-
version: "0.
|
|
8204
|
+
version: "0.9.0",
|
|
8167
8205
|
private: true,
|
|
8168
8206
|
type: "module",
|
|
8169
8207
|
main: "src/index.ts",
|
|
@@ -12257,9 +12295,11 @@ var legacyClientNotificationMethods = new Set([
|
|
|
12257
12295
|
|
|
12258
12296
|
// ../daemon/src/core/errors.ts
|
|
12259
12297
|
class UserError extends Error {
|
|
12260
|
-
|
|
12298
|
+
delivered;
|
|
12299
|
+
constructor(message, about = {}) {
|
|
12261
12300
|
super(message);
|
|
12262
12301
|
this.name = "UserError";
|
|
12302
|
+
this.delivered = about.delivered ?? false;
|
|
12263
12303
|
}
|
|
12264
12304
|
}
|
|
12265
12305
|
|
|
@@ -12512,18 +12552,177 @@ function spawnAcp(app, launch, cwd, tag) {
|
|
|
12512
12552
|
|
|
12513
12553
|
// ../daemon/src/agent/attachments.ts
|
|
12514
12554
|
import { createHash } from "crypto";
|
|
12515
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
12555
|
+
import { copyFile as copyFile2, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
12556
|
+
import { join as join2 } from "path";
|
|
12557
|
+
|
|
12558
|
+
// ../daemon/src/agent/uploads.ts
|
|
12559
|
+
import { randomBytes } from "crypto";
|
|
12560
|
+
import { mkdirSync, readdirSync, rmSync } from "fs";
|
|
12561
|
+
import { copyFile } from "fs/promises";
|
|
12516
12562
|
import { join } from "path";
|
|
12517
|
-
var
|
|
12563
|
+
var IMAGE_EXT = {
|
|
12518
12564
|
"image/png": "png",
|
|
12519
12565
|
"image/jpeg": "jpg",
|
|
12520
12566
|
"image/gif": "gif",
|
|
12521
12567
|
"image/webp": "webp"
|
|
12522
12568
|
};
|
|
12569
|
+
var MIME_OF_EXT = new Map(IMAGE_MIME_TYPES.map((mime) => [IMAGE_EXT[mime], mime]));
|
|
12570
|
+
var OTHER_EXT = "bin";
|
|
12571
|
+
var NAME = /^([A-Za-z0-9_-]{1,64})\.(png|jpg|gif|webp|bin)$/;
|
|
12572
|
+
var UNNAMED_MS = 10 * 60000;
|
|
12573
|
+
var GONE = "an attachment is no longer on this machine; attach it again";
|
|
12574
|
+
var isUploadKind = (kind) => kind === "image" || kind === "file";
|
|
12575
|
+
function uploadIds(items) {
|
|
12576
|
+
return (items ?? []).flatMap((a) => a.kind === "image" || a.kind === "file" ? [a.upload] : []);
|
|
12577
|
+
}
|
|
12578
|
+
|
|
12579
|
+
class UploadStore {
|
|
12580
|
+
dir;
|
|
12581
|
+
unnamedMs;
|
|
12582
|
+
files = new Map;
|
|
12583
|
+
holds = new Map;
|
|
12584
|
+
named = new Set;
|
|
12585
|
+
constructor(dir, unnamedMs = UNNAMED_MS) {
|
|
12586
|
+
this.dir = dir;
|
|
12587
|
+
this.unnamedMs = unnamedMs;
|
|
12588
|
+
mkdirSync(dir, { recursive: true });
|
|
12589
|
+
for (const file of readdirSync(dir)) {
|
|
12590
|
+
const id = NAME.exec(file)?.[1];
|
|
12591
|
+
if (id)
|
|
12592
|
+
this.files.set(id, file);
|
|
12593
|
+
}
|
|
12594
|
+
}
|
|
12595
|
+
async put(kind, mime, body) {
|
|
12596
|
+
const image = IMAGE_EXT[mime];
|
|
12597
|
+
if (kind === "image" && !image)
|
|
12598
|
+
throw new UserError("not an image format the models accept");
|
|
12599
|
+
const cap = kind === "image" ? IMAGE_MAX_BYTES : UPLOAD_MAX_BYTES;
|
|
12600
|
+
const upload = randomBytes(12).toString("base64url");
|
|
12601
|
+
const file = `${upload}.${kind === "image" ? image : OTHER_EXT}`;
|
|
12602
|
+
const path = join(this.dir, file);
|
|
12603
|
+
const writer = Bun.file(path).writer();
|
|
12604
|
+
let decoder = kind === "file" ? new TextDecoder("utf-8", { fatal: true }) : null;
|
|
12605
|
+
let bytes = 0;
|
|
12606
|
+
try {
|
|
12607
|
+
for await (const chunk of body ?? []) {
|
|
12608
|
+
bytes += chunk.byteLength;
|
|
12609
|
+
if (bytes > cap)
|
|
12610
|
+
throw new UserError(`larger than ${fmtBytes(cap)}`);
|
|
12611
|
+
writer.write(chunk);
|
|
12612
|
+
await writer.flush();
|
|
12613
|
+
if (decoder && (bytes > FILE_MAX_CHARS || !decodes(decoder, chunk)))
|
|
12614
|
+
decoder = null;
|
|
12615
|
+
}
|
|
12616
|
+
if (bytes === 0)
|
|
12617
|
+
throw new UserError("empty file");
|
|
12618
|
+
await writer.end();
|
|
12619
|
+
} catch (e) {
|
|
12620
|
+
await Promise.resolve(writer.end()).catch(() => {});
|
|
12621
|
+
rmSync(path, { force: true });
|
|
12622
|
+
throw e;
|
|
12623
|
+
}
|
|
12624
|
+
this.files.set(upload, file);
|
|
12625
|
+
this.expireUnnamed(upload);
|
|
12626
|
+
return { upload, bytes, text: decoder !== null };
|
|
12627
|
+
}
|
|
12628
|
+
async adopt(from, image) {
|
|
12629
|
+
const upload = randomBytes(12).toString("base64url");
|
|
12630
|
+
const file = `${upload}.${image ? IMAGE_EXT[image] : OTHER_EXT}`;
|
|
12631
|
+
await copyFile(from, join(this.dir, file));
|
|
12632
|
+
this.files.set(upload, file);
|
|
12633
|
+
this.expireUnnamed(upload);
|
|
12634
|
+
return upload;
|
|
12635
|
+
}
|
|
12636
|
+
expireUnnamed(id) {
|
|
12637
|
+
const timer = setTimeout(() => {
|
|
12638
|
+
if (!this.named.has(id) && !this.holds.has(id))
|
|
12639
|
+
this.remove(id);
|
|
12640
|
+
}, this.unnamedMs);
|
|
12641
|
+
timer.unref?.();
|
|
12642
|
+
}
|
|
12643
|
+
path(id) {
|
|
12644
|
+
const file = this.files.get(id);
|
|
12645
|
+
return file ? join(this.dir, file) : null;
|
|
12646
|
+
}
|
|
12647
|
+
imageType(id) {
|
|
12648
|
+
const file = this.files.get(id);
|
|
12649
|
+
return file && MIME_OF_EXT.get(file.slice(file.lastIndexOf(".") + 1)) || null;
|
|
12650
|
+
}
|
|
12651
|
+
has(id) {
|
|
12652
|
+
return this.files.has(id);
|
|
12653
|
+
}
|
|
12654
|
+
hold(ids) {
|
|
12655
|
+
if (ids.some((id) => !this.files.has(id)))
|
|
12656
|
+
throw new UserError(GONE);
|
|
12657
|
+
for (const id of ids)
|
|
12658
|
+
this.holds.set(id, (this.holds.get(id) ?? 0) + 1);
|
|
12659
|
+
}
|
|
12660
|
+
release(ids) {
|
|
12661
|
+
for (const id of ids) {
|
|
12662
|
+
const left = (this.holds.get(id) ?? 0) - 1;
|
|
12663
|
+
if (left > 0) {
|
|
12664
|
+
this.holds.set(id, left);
|
|
12665
|
+
continue;
|
|
12666
|
+
}
|
|
12667
|
+
this.holds.delete(id);
|
|
12668
|
+
if (!this.named.has(id))
|
|
12669
|
+
this.remove(id);
|
|
12670
|
+
}
|
|
12671
|
+
}
|
|
12672
|
+
keep(named) {
|
|
12673
|
+
const before = this.named;
|
|
12674
|
+
this.named = new Set(named);
|
|
12675
|
+
for (const id of before)
|
|
12676
|
+
if (!this.named.has(id) && !this.holds.has(id))
|
|
12677
|
+
this.remove(id);
|
|
12678
|
+
}
|
|
12679
|
+
sweep(named) {
|
|
12680
|
+
this.named = new Set(named);
|
|
12681
|
+
for (const id of [...this.files.keys()])
|
|
12682
|
+
if (!this.named.has(id))
|
|
12683
|
+
this.remove(id);
|
|
12684
|
+
}
|
|
12685
|
+
remove(id) {
|
|
12686
|
+
const file = this.files.get(id);
|
|
12687
|
+
if (!file)
|
|
12688
|
+
return;
|
|
12689
|
+
this.files.delete(id);
|
|
12690
|
+
try {
|
|
12691
|
+
rmSync(join(this.dir, file), { force: true });
|
|
12692
|
+
} catch (e) {
|
|
12693
|
+
log.warn("uploads", `could not delete ${file}`, e);
|
|
12694
|
+
}
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12697
|
+
function decodes(decoder, chunk) {
|
|
12698
|
+
try {
|
|
12699
|
+
decoder.decode(chunk, { stream: true });
|
|
12700
|
+
return !chunk.includes(0);
|
|
12701
|
+
} catch {
|
|
12702
|
+
return false;
|
|
12703
|
+
}
|
|
12704
|
+
}
|
|
12705
|
+
|
|
12706
|
+
// ../daemon/src/agent/attachments.ts
|
|
12523
12707
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12524
|
-
var
|
|
12708
|
+
var NAME_MAX = 80;
|
|
12709
|
+
var DRAWN = /^(?:\d{1,6}|[0-9a-f]{16})\.(png|jpg|gif|webp|txt)$/;
|
|
12710
|
+
var NAMED = new RegExp(`^\\d{1,6}-[A-Za-z0-9._-]{1,${NAME_MAX}}$`);
|
|
12711
|
+
var isFile = (file) => DRAWN.test(file) || NAMED.test(file);
|
|
12712
|
+
var MIME = { png: "image/png", jpg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
|
|
12713
|
+
function drawnType(file) {
|
|
12714
|
+
return MIME[DRAWN.exec(file)?.[1] ?? ""] ?? null;
|
|
12715
|
+
}
|
|
12716
|
+
function storedName(name) {
|
|
12717
|
+
const safe = name.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^\.+/, "") || "file";
|
|
12718
|
+
if (safe.length <= NAME_MAX)
|
|
12719
|
+
return safe;
|
|
12720
|
+
const dot = safe.lastIndexOf(".");
|
|
12721
|
+
const ext = dot > 0 && safe.length - dot <= 16 ? safe.slice(dot) : "";
|
|
12722
|
+
return safe.slice(0, NAME_MAX - ext.length) + ext;
|
|
12723
|
+
}
|
|
12525
12724
|
function toolImage(data, mimeType) {
|
|
12526
|
-
const ext =
|
|
12725
|
+
const ext = IMAGE_EXT[mimeType];
|
|
12527
12726
|
if (!ext)
|
|
12528
12727
|
return null;
|
|
12529
12728
|
const bytes = Buffer.from(data, "base64");
|
|
@@ -12532,27 +12731,40 @@ function toolImage(data, mimeType) {
|
|
|
12532
12731
|
const file = `${createHash("sha256").update(bytes).digest("hex").slice(0, 16)}.${ext}`;
|
|
12533
12732
|
return { ref: { file, mimeType, bytes: bytes.length }, bytes };
|
|
12534
12733
|
}
|
|
12535
|
-
var isAttachmentFile = (file) =>
|
|
12734
|
+
var isAttachmentFile = (file) => isFile(file);
|
|
12536
12735
|
function attachmentsDirFor(attachmentsDir, worktreeId) {
|
|
12537
|
-
return
|
|
12736
|
+
return join2(attachmentsDir, worktreeId);
|
|
12737
|
+
}
|
|
12738
|
+
var GONE2 = "the upload is gone; attach it again";
|
|
12739
|
+
async function shortText(path, bytes) {
|
|
12740
|
+
if (bytes > FILE_INLINE_CHARS * 4)
|
|
12741
|
+
return null;
|
|
12742
|
+
try {
|
|
12743
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(await readFile(path));
|
|
12744
|
+
return text.length <= FILE_INLINE_CHARS ? text : null;
|
|
12745
|
+
} catch {
|
|
12746
|
+
return null;
|
|
12747
|
+
}
|
|
12538
12748
|
}
|
|
12539
12749
|
|
|
12540
12750
|
class AttachmentStore {
|
|
12541
12751
|
dir;
|
|
12752
|
+
uploads;
|
|
12542
12753
|
pending = new Map;
|
|
12543
|
-
constructor(dir) {
|
|
12754
|
+
constructor(dir, uploads) {
|
|
12544
12755
|
this.dir = dir;
|
|
12756
|
+
this.uploads = uploads;
|
|
12545
12757
|
}
|
|
12546
12758
|
async putToolImage(worktreeId, file, bytes) {
|
|
12547
|
-
if (!ID.test(worktreeId) || !
|
|
12759
|
+
if (!ID.test(worktreeId) || !DRAWN.test(file))
|
|
12548
12760
|
throw new UserError("bad tool image");
|
|
12549
|
-
const path =
|
|
12761
|
+
const path = join2(attachmentsDirFor(this.dir, worktreeId), file);
|
|
12550
12762
|
if (this.pending.has(path) || await Bun.file(path).exists())
|
|
12551
12763
|
return;
|
|
12552
|
-
await this.write(worktreeId, file, bytes);
|
|
12764
|
+
await this.write(worktreeId, file, (path) => writeFile(path, bytes));
|
|
12553
12765
|
}
|
|
12554
12766
|
whenWritten(worktreeId, file) {
|
|
12555
|
-
return this.pending.get(
|
|
12767
|
+
return this.pending.get(join2(attachmentsDirFor(this.dir, worktreeId), file)) ?? Promise.resolve();
|
|
12556
12768
|
}
|
|
12557
12769
|
async put(worktreeId, n, input) {
|
|
12558
12770
|
if (!ID.test(worktreeId))
|
|
@@ -12562,19 +12774,23 @@ class AttachmentStore {
|
|
|
12562
12774
|
return this.putImage(worktreeId, n, input);
|
|
12563
12775
|
case "paste":
|
|
12564
12776
|
return this.putText(worktreeId, n, input);
|
|
12777
|
+
case "file":
|
|
12778
|
+
return this.putFile(worktreeId, n, input);
|
|
12565
12779
|
case "pick":
|
|
12566
12780
|
return { kind: "pick", ref: { ...input, n } };
|
|
12567
12781
|
}
|
|
12568
12782
|
}
|
|
12569
12783
|
async putImage(worktreeId, n, img) {
|
|
12570
|
-
const
|
|
12571
|
-
if (
|
|
12572
|
-
throw new UserError(`${img.name}:
|
|
12573
|
-
const
|
|
12574
|
-
|
|
12784
|
+
const from = this.uploads.path(img.upload);
|
|
12785
|
+
if (!from || this.uploads.imageType(img.upload) !== img.mimeType)
|
|
12786
|
+
throw new UserError(`${img.name}: ${GONE2}`);
|
|
12787
|
+
const bytes = await readFile(from);
|
|
12788
|
+
const file = `${n}.${IMAGE_EXT[img.mimeType]}`;
|
|
12789
|
+
const path = await this.write(worktreeId, file, (to) => writeFile(to, bytes));
|
|
12575
12790
|
return {
|
|
12576
12791
|
kind: "image",
|
|
12577
12792
|
bytes,
|
|
12793
|
+
path,
|
|
12578
12794
|
ref: {
|
|
12579
12795
|
kind: "image",
|
|
12580
12796
|
n,
|
|
@@ -12587,28 +12803,78 @@ class AttachmentStore {
|
|
|
12587
12803
|
}
|
|
12588
12804
|
};
|
|
12589
12805
|
}
|
|
12806
|
+
async putFile(worktreeId, n, input) {
|
|
12807
|
+
const from = this.uploads.path(input.upload);
|
|
12808
|
+
if (!from)
|
|
12809
|
+
throw new UserError(`${input.name}: ${GONE2}`);
|
|
12810
|
+
const file = `${n}-${storedName(input.name)}`;
|
|
12811
|
+
const path = await this.write(worktreeId, file, (to) => copyFile2(from, to));
|
|
12812
|
+
const bytes = (await stat(path)).size;
|
|
12813
|
+
const text = input.text ? await shortText(path, bytes) : null;
|
|
12814
|
+
return {
|
|
12815
|
+
kind: "file",
|
|
12816
|
+
path,
|
|
12817
|
+
...text === null ? {} : { text },
|
|
12818
|
+
ref: { kind: "file", n, name: input.name, bytes, text: input.text, file }
|
|
12819
|
+
};
|
|
12820
|
+
}
|
|
12590
12821
|
async putText(worktreeId, n, { text, name, source }) {
|
|
12591
12822
|
if (text.length === 0)
|
|
12592
12823
|
throw new UserError("empty paste");
|
|
12593
12824
|
const file = `${n}.txt`;
|
|
12594
|
-
await this.write(worktreeId, file, text);
|
|
12825
|
+
await this.write(worktreeId, file, (to) => writeFile(to, text));
|
|
12595
12826
|
return {
|
|
12596
12827
|
kind: "paste",
|
|
12597
12828
|
text,
|
|
12598
12829
|
ref: { kind: "paste", n, ...name ? { name } : {}, ...source ? { source } : {}, ...pasteSummary(text), file }
|
|
12599
12830
|
};
|
|
12600
12831
|
}
|
|
12601
|
-
|
|
12832
|
+
async reattach(worktreeId, ref, archived) {
|
|
12833
|
+
const again = await this.inputOf(worktreeId, ref, archived);
|
|
12834
|
+
const parsed = attachmentInputSchema.safeParse(again);
|
|
12835
|
+
return parsed.success ? parsed.data : null;
|
|
12836
|
+
}
|
|
12837
|
+
async inputOf(worktreeId, ref, archived) {
|
|
12838
|
+
if (ref.kind === "pick") {
|
|
12839
|
+
const { n: _n, ...pick } = ref;
|
|
12840
|
+
return pick;
|
|
12841
|
+
}
|
|
12842
|
+
let path = null;
|
|
12843
|
+
for (const at of [this.fileFor(worktreeId, ref.file), archived?.(ref.file) ?? null]) {
|
|
12844
|
+
if (at && await Bun.file(at).exists()) {
|
|
12845
|
+
path = at;
|
|
12846
|
+
break;
|
|
12847
|
+
}
|
|
12848
|
+
}
|
|
12849
|
+
if (!path)
|
|
12850
|
+
return null;
|
|
12851
|
+
if (ref.kind === "paste") {
|
|
12852
|
+
const { name, source } = ref;
|
|
12853
|
+
return { kind: "paste", text: await readFile(path, "utf8"), name, source };
|
|
12854
|
+
}
|
|
12855
|
+
if (ref.kind === "file") {
|
|
12856
|
+
const upload = await this.uploads.adopt(path, null);
|
|
12857
|
+
return { kind: "file", upload, name: ref.name, bytes: ref.bytes, text: ref.text };
|
|
12858
|
+
}
|
|
12859
|
+
const mimeType = IMAGE_MIME_TYPES.find((m) => m === ref.mimeType);
|
|
12860
|
+
if (!mimeType)
|
|
12861
|
+
return null;
|
|
12862
|
+
const upload = await this.uploads.adopt(path, mimeType);
|
|
12863
|
+
const { name, bytes, width, height } = ref;
|
|
12864
|
+
return { kind: "image", upload, name, mimeType, bytes, width, height };
|
|
12865
|
+
}
|
|
12866
|
+
async write(worktreeId, file, fill) {
|
|
12602
12867
|
const wtDir = attachmentsDirFor(this.dir, worktreeId);
|
|
12603
|
-
const path =
|
|
12604
|
-
const done = mkdir(wtDir, { recursive: true }).then(() =>
|
|
12868
|
+
const path = join2(wtDir, file);
|
|
12869
|
+
const done = mkdir(wtDir, { recursive: true }).then(() => fill(path)).finally(() => this.pending.delete(path));
|
|
12605
12870
|
this.pending.set(path, done);
|
|
12606
|
-
|
|
12871
|
+
await done;
|
|
12872
|
+
return path;
|
|
12607
12873
|
}
|
|
12608
12874
|
fileFor(worktreeId, file) {
|
|
12609
|
-
if (!ID.test(worktreeId) || !
|
|
12875
|
+
if (!ID.test(worktreeId) || !isFile(file))
|
|
12610
12876
|
return null;
|
|
12611
|
-
return
|
|
12877
|
+
return join2(attachmentsDirFor(this.dir, worktreeId), file);
|
|
12612
12878
|
}
|
|
12613
12879
|
}
|
|
12614
12880
|
|
|
@@ -12765,9 +13031,9 @@ class OptionProbe {
|
|
|
12765
13031
|
}
|
|
12766
13032
|
|
|
12767
13033
|
// ../daemon/src/agent/registry.ts
|
|
12768
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
13034
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
12769
13035
|
import { homedir as homedir3 } from "os";
|
|
12770
|
-
import { dirname as dirname3, isAbsolute, join as
|
|
13036
|
+
import { dirname as dirname3, isAbsolute, join as join6, resolve as resolve3 } from "path";
|
|
12771
13037
|
|
|
12772
13038
|
// ../daemon/src/core/cloud.ts
|
|
12773
13039
|
var enabled = process.env.TOYON_CLOUD === "1";
|
|
@@ -12794,7 +13060,7 @@ var cloud = {
|
|
|
12794
13060
|
import { spawn as spawn2 } from "child_process";
|
|
12795
13061
|
import { createHash as createHash2 } from "crypto";
|
|
12796
13062
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
12797
|
-
import { join as
|
|
13063
|
+
import { join as join3 } from "path";
|
|
12798
13064
|
|
|
12799
13065
|
// ../daemon/src/core/metrics.ts
|
|
12800
13066
|
var WARN_MS = 50;
|
|
@@ -12836,7 +13102,7 @@ function lockfileHash(dir) {
|
|
|
12836
13102
|
const h = createHash2("sha1");
|
|
12837
13103
|
for (const f of LOCKFILES) {
|
|
12838
13104
|
try {
|
|
12839
|
-
h.update(readFileSync2(
|
|
13105
|
+
h.update(readFileSync2(join3(dir, f)));
|
|
12840
13106
|
} catch {}
|
|
12841
13107
|
}
|
|
12842
13108
|
return h.digest("hex");
|
|
@@ -13151,7 +13417,7 @@ function confine(launch, b, extraWrite = [], platform = process.platform, which
|
|
|
13151
13417
|
}
|
|
13152
13418
|
|
|
13153
13419
|
// ../daemon/src/agent/native.ts
|
|
13154
|
-
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
13420
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
13155
13421
|
var OS = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
13156
13422
|
function nativePackage(pkg, host) {
|
|
13157
13423
|
const os = OS[host.platform];
|
|
@@ -13174,7 +13440,7 @@ function hostTraits() {
|
|
|
13174
13440
|
}
|
|
13175
13441
|
function muslLoader() {
|
|
13176
13442
|
try {
|
|
13177
|
-
return existsSync2("/lib") &&
|
|
13443
|
+
return existsSync2("/lib") && readdirSync2("/lib").some((f) => f.startsWith("ld-musl-"));
|
|
13178
13444
|
} catch {
|
|
13179
13445
|
return false;
|
|
13180
13446
|
}
|
|
@@ -13244,49 +13510,52 @@ function opencodeEnv() {
|
|
|
13244
13510
|
}
|
|
13245
13511
|
|
|
13246
13512
|
// ../daemon/src/agent/sandbox.ts
|
|
13247
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
13513
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
13248
13514
|
import { homedir as homedir2, tmpdir } from "os";
|
|
13249
|
-
import { join as
|
|
13515
|
+
import { join as join5, resolve as resolve2 } from "path";
|
|
13250
13516
|
|
|
13251
13517
|
// ../daemon/src/core/paths.ts
|
|
13252
|
-
import { mkdirSync } from "fs";
|
|
13518
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
13253
13519
|
import { homedir } from "os";
|
|
13254
|
-
import { join as
|
|
13255
|
-
function makePaths(home = process.env.TOYON_HOME ??
|
|
13520
|
+
import { join as join4 } from "path";
|
|
13521
|
+
function makePaths(home = process.env.TOYON_HOME ?? join4(homedir(), ".toyon")) {
|
|
13256
13522
|
return {
|
|
13257
13523
|
home,
|
|
13258
|
-
stateFile:
|
|
13259
|
-
draftsFile:
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13524
|
+
stateFile: join4(home, DAEMON_FILES.state),
|
|
13525
|
+
draftsFile: join4(home, DAEMON_FILES.drafts),
|
|
13526
|
+
draftAttachmentsFile: join4(home, "draft-attachments.json"),
|
|
13527
|
+
tokenFile: join4(home, DAEMON_FILES.token),
|
|
13528
|
+
pidFile: join4(home, DAEMON_FILES.pid),
|
|
13529
|
+
logFile: join4(home, DAEMON_FILES.log),
|
|
13530
|
+
remoteFile: join4(home, DAEMON_FILES.remote),
|
|
13531
|
+
transcriptsDir: join4(home, "transcripts"),
|
|
13532
|
+
attachmentsDir: join4(home, "attachments"),
|
|
13533
|
+
uploadsDir: join4(home, "uploads"),
|
|
13534
|
+
archiveDir: join4(home, "archive"),
|
|
13535
|
+
worktreesDir: join4(home, "worktrees.noindex"),
|
|
13536
|
+
cacheDir: join4(home, "cache.noindex"),
|
|
13537
|
+
themesDir: join4(home, "themes"),
|
|
13538
|
+
agentsDir: process.env.TOYON_AGENTS_DIR ?? join4(home, "agents"),
|
|
13539
|
+
agentsFile: join4(home, "agents.json"),
|
|
13540
|
+
scratchDir: join4(home, "scratch"),
|
|
13541
|
+
gitCredentialsFile: join4(home, "git-credentials")
|
|
13274
13542
|
};
|
|
13275
13543
|
}
|
|
13276
13544
|
function ensureDirs(p) {
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13545
|
+
mkdirSync2(p.home, { recursive: true, mode: 448 });
|
|
13546
|
+
mkdirSync2(p.transcriptsDir, { recursive: true });
|
|
13547
|
+
mkdirSync2(p.attachmentsDir, { recursive: true });
|
|
13548
|
+
mkdirSync2(p.uploadsDir, { recursive: true });
|
|
13549
|
+
mkdirSync2(p.archiveDir, { recursive: true });
|
|
13550
|
+
mkdirSync2(p.worktreesDir, { recursive: true });
|
|
13551
|
+
mkdirSync2(p.cacheDir, { recursive: true });
|
|
13552
|
+
mkdirSync2(p.themesDir, { recursive: true });
|
|
13553
|
+
mkdirSync2(p.agentsDir, { recursive: true });
|
|
13554
|
+
mkdirSync2(p.scratchDir, { recursive: true });
|
|
13286
13555
|
}
|
|
13287
13556
|
|
|
13288
13557
|
// ../daemon/src/git/exclude.ts
|
|
13289
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
13558
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
13290
13559
|
import { dirname } from "path";
|
|
13291
13560
|
async function excludeFile(cwd) {
|
|
13292
13561
|
const r = await git(cwd, "rev-parse", "--path-format=absolute", "--git-path", "info/exclude");
|
|
@@ -13303,7 +13572,7 @@ async function excludeFromGit(cwd, pattern) {
|
|
|
13303
13572
|
if (current.split(`
|
|
13304
13573
|
`).some((l) => l.trim() === pattern))
|
|
13305
13574
|
return;
|
|
13306
|
-
|
|
13575
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
13307
13576
|
writeFileSync(file, `${current}${current && !current.endsWith(`
|
|
13308
13577
|
`) ? `
|
|
13309
13578
|
` : ""}${pattern}
|
|
@@ -13329,7 +13598,7 @@ async function excludeBlock(cwd, name, patterns) {
|
|
|
13329
13598
|
` : "";
|
|
13330
13599
|
if (text === current)
|
|
13331
13600
|
return;
|
|
13332
|
-
|
|
13601
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
13333
13602
|
writeFileSync(file, text);
|
|
13334
13603
|
}
|
|
13335
13604
|
async function unexcludeFromGit(cwd, pattern) {
|
|
@@ -13396,7 +13665,7 @@ async function prepareLaunch(cwd, spec) {
|
|
|
13396
13665
|
log.warn(cwd, `agent ${spec.id} runs without an OS sandbox`);
|
|
13397
13666
|
return { bounds, env: setup?.env ?? {} };
|
|
13398
13667
|
}
|
|
13399
|
-
var SETTINGS_REL =
|
|
13668
|
+
var SETTINGS_REL = join5(".claude", "settings.local.json");
|
|
13400
13669
|
function claudeLocalSettings(b) {
|
|
13401
13670
|
return {
|
|
13402
13671
|
sandbox: {
|
|
@@ -13410,8 +13679,8 @@ function claudeLocalSettings(b) {
|
|
|
13410
13679
|
};
|
|
13411
13680
|
}
|
|
13412
13681
|
async function writeClaudeLocalSettings(cwd, b) {
|
|
13413
|
-
const file =
|
|
13414
|
-
|
|
13682
|
+
const file = join5(cwd, SETTINGS_REL);
|
|
13683
|
+
mkdirSync4(join5(cwd, ".claude"), { recursive: true });
|
|
13415
13684
|
let existing = {};
|
|
13416
13685
|
if (existsSync5(file)) {
|
|
13417
13686
|
try {
|
|
@@ -13433,7 +13702,7 @@ async function writeClaudeLocalSettings(cwd, b) {
|
|
|
13433
13702
|
writeFileSync2(tmp, `${JSON.stringify(next, null, 2)}
|
|
13434
13703
|
`);
|
|
13435
13704
|
renameSync(tmp, file);
|
|
13436
|
-
if (existsSync5(
|
|
13705
|
+
if (existsSync5(join5(cwd, ".git")))
|
|
13437
13706
|
await excludeFromGit(cwd, SETTINGS_REL);
|
|
13438
13707
|
}
|
|
13439
13708
|
|
|
@@ -13558,12 +13827,12 @@ class AgentRegistry {
|
|
|
13558
13827
|
const name = this.packageName(spec);
|
|
13559
13828
|
if (!name || spec.run.kind === "command")
|
|
13560
13829
|
return null;
|
|
13561
|
-
const pkgDir =
|
|
13830
|
+
const pkgDir = join6(this.dir, spec.id, "node_modules", name);
|
|
13562
13831
|
if (spec.run.kind === "npm-native") {
|
|
13563
|
-
const bin =
|
|
13832
|
+
const bin = join6(pkgDir, spec.run.bin);
|
|
13564
13833
|
return existsSync6(bin) ? bin : null;
|
|
13565
13834
|
}
|
|
13566
|
-
const pkgJson =
|
|
13835
|
+
const pkgJson = join6(pkgDir, "package.json");
|
|
13567
13836
|
if (!existsSync6(pkgJson))
|
|
13568
13837
|
return null;
|
|
13569
13838
|
try {
|
|
@@ -13571,7 +13840,7 @@ class AgentRegistry {
|
|
|
13571
13840
|
const rel = typeof meta.bin === "string" ? meta.bin : meta.bin?.[spec.run.bin];
|
|
13572
13841
|
if (!rel)
|
|
13573
13842
|
return null;
|
|
13574
|
-
const script =
|
|
13843
|
+
const script = join6(dirname3(pkgJson), rel);
|
|
13575
13844
|
return existsSync6(script) ? script : null;
|
|
13576
13845
|
} catch (e) {
|
|
13577
13846
|
log.warn("agents", `cannot read ${pkgJson}`, e);
|
|
@@ -13625,10 +13894,10 @@ class AgentRegistry {
|
|
|
13625
13894
|
if (this.installed(spec) && this.installedVersion(spec) === spec.run.version)
|
|
13626
13895
|
return Promise.resolve();
|
|
13627
13896
|
const { version } = spec.run;
|
|
13628
|
-
const dir =
|
|
13897
|
+
const dir = join6(this.dir, id);
|
|
13629
13898
|
const p = (async () => {
|
|
13630
|
-
|
|
13631
|
-
writeFileSync3(
|
|
13899
|
+
mkdirSync5(dir, { recursive: true });
|
|
13900
|
+
writeFileSync3(join6(dir, "package.json"), `${JSON.stringify({ name: `toyon-agent-${id}`, private: true, dependencies: { [pkg]: version } }, null, 2)}
|
|
13632
13901
|
`);
|
|
13633
13902
|
log.info("agents", `installing ${pkg}@${version} for ${id}`);
|
|
13634
13903
|
const r = await this.installer(dir, pkg, version);
|
|
@@ -13652,7 +13921,7 @@ class AgentRegistry {
|
|
|
13652
13921
|
const name = this.packageName(spec);
|
|
13653
13922
|
if (!name)
|
|
13654
13923
|
return null;
|
|
13655
|
-
const pkgJson =
|
|
13924
|
+
const pkgJson = join6(this.dir, spec.id, "node_modules", name, "package.json");
|
|
13656
13925
|
try {
|
|
13657
13926
|
return JSON.parse(readFileSync6(pkgJson, "utf8")).version ?? null;
|
|
13658
13927
|
} catch {
|
|
@@ -13667,6 +13936,19 @@ class AgentRegistry {
|
|
|
13667
13936
|
await this.install(id);
|
|
13668
13937
|
}
|
|
13669
13938
|
}
|
|
13939
|
+
adapters() {
|
|
13940
|
+
const out = [];
|
|
13941
|
+
for (const spec of this.list()) {
|
|
13942
|
+
if (spec.run.kind === "command" || !this.packageName(spec))
|
|
13943
|
+
continue;
|
|
13944
|
+
const installed = this.installed(spec) ? this.installedVersion(spec) : null;
|
|
13945
|
+
const error = this.installErrors.get(spec.id);
|
|
13946
|
+
if (spec.onDemand && installed === null && !error)
|
|
13947
|
+
continue;
|
|
13948
|
+
out.push({ id: spec.id, installed, pinned: spec.run.version, ...error ? { error } : {} });
|
|
13949
|
+
}
|
|
13950
|
+
return out;
|
|
13951
|
+
}
|
|
13670
13952
|
infos() {
|
|
13671
13953
|
return this.list().map((spec) => {
|
|
13672
13954
|
const reason = this.unavailable(spec);
|
|
@@ -13902,6 +14184,9 @@ function previewContext(p) {
|
|
|
13902
14184
|
case "setup":
|
|
13903
14185
|
line = "Toyon is setting this worktree up and starts the preview when that is done; the next message will say where it answers.";
|
|
13904
14186
|
break;
|
|
14187
|
+
case "parked":
|
|
14188
|
+
line = "The preview is off: this worktree came back from the archive, and Toyon starts its dev servers when you first write a file or when the user asks for them. Nothing answers until then.";
|
|
14189
|
+
break;
|
|
13905
14190
|
case "starting":
|
|
13906
14191
|
line = `Toyon is starting the preview; it answers${at} once it is up.`;
|
|
13907
14192
|
break;
|
|
@@ -13916,8 +14201,12 @@ function previewContext(p) {
|
|
|
13916
14201
|
}
|
|
13917
14202
|
return line;
|
|
13918
14203
|
}
|
|
13919
|
-
function imageCaption(ref) {
|
|
13920
|
-
return `${attachmentLabel("image", ref.n)}: ${ref.name} (${ref.width}\xD7${ref.height})`;
|
|
14204
|
+
function imageCaption(ref, path) {
|
|
14205
|
+
return `${attachmentLabel("image", ref.n)}: ${ref.name} (${ref.width}\xD7${ref.height}), saved at ${path}`;
|
|
14206
|
+
}
|
|
14207
|
+
function fileCaption(ref, path, inline = false) {
|
|
14208
|
+
const at = `A file the user attached: ${ref.name} (${fmtBytes(ref.bytes)}), saved at ${path}.`;
|
|
14209
|
+
return inline ? at : `${at} Read or search it there.`;
|
|
13921
14210
|
}
|
|
13922
14211
|
function pasteCaption(ref) {
|
|
13923
14212
|
const label = attachmentLabel("paste", ref.n);
|
|
@@ -13963,7 +14252,7 @@ function attachmentBlocks(a) {
|
|
|
13963
14252
|
switch (a.kind) {
|
|
13964
14253
|
case "image":
|
|
13965
14254
|
return [
|
|
13966
|
-
textBlock(imageCaption(a.ref)),
|
|
14255
|
+
textBlock(imageCaption(a.ref, a.path)),
|
|
13967
14256
|
{ type: "image", mimeType: a.ref.mimeType, data: a.bytes.toString("base64") }
|
|
13968
14257
|
];
|
|
13969
14258
|
case "paste":
|
|
@@ -13971,6 +14260,13 @@ function attachmentBlocks(a) {
|
|
|
13971
14260
|
<pasted-text ${a.ref.n}>
|
|
13972
14261
|
${a.text}
|
|
13973
14262
|
</pasted-text>`)];
|
|
14263
|
+
case "file":
|
|
14264
|
+
return [
|
|
14265
|
+
textBlock(a.text === undefined ? fileCaption(a.ref, a.path) : `${fileCaption(a.ref, a.path, true)}
|
|
14266
|
+
<attached-file ${a.ref.n}>
|
|
14267
|
+
${a.text}
|
|
14268
|
+
</attached-file>`)
|
|
14269
|
+
];
|
|
13974
14270
|
case "pick":
|
|
13975
14271
|
return [textBlock(pickCaption(a.ref))];
|
|
13976
14272
|
}
|
|
@@ -13979,9 +14275,9 @@ ${a.text}
|
|
|
13979
14275
|
// ../daemon/src/agent/transcript.ts
|
|
13980
14276
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
13981
14277
|
import { appendFile } from "fs/promises";
|
|
13982
|
-
import { join as
|
|
14278
|
+
import { join as join7 } from "path";
|
|
13983
14279
|
function transcriptPathFor(transcriptsDir, worktreeId) {
|
|
13984
|
-
return
|
|
14280
|
+
return join7(transcriptsDir, `${worktreeId}.jsonl`);
|
|
13985
14281
|
}
|
|
13986
14282
|
function coalesce(entries) {
|
|
13987
14283
|
const out = [];
|
|
@@ -14136,7 +14432,7 @@ async function cleanUp(ctx, a, sessionId) {
|
|
|
14136
14432
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
14137
14433
|
import { open as open2 } from "fs/promises";
|
|
14138
14434
|
import { homedir as homedir4 } from "os";
|
|
14139
|
-
import { basename as basename2, dirname as dirname4, join as
|
|
14435
|
+
import { basename as basename2, dirname as dirname4, join as join8 } from "path";
|
|
14140
14436
|
var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
|
|
14141
14437
|
function backgroundStart(input, output) {
|
|
14142
14438
|
const flag = input && typeof input === "object" ? input.run_in_background : undefined;
|
|
@@ -14145,15 +14441,18 @@ function backgroundStart(input, output) {
|
|
|
14145
14441
|
const m = STARTED.exec(output);
|
|
14146
14442
|
return m ? { taskId: m[1], file: m[2] } : null;
|
|
14147
14443
|
}
|
|
14444
|
+
function spawnDetached(output) {
|
|
14445
|
+
return output.includes("Async agent launched successfully");
|
|
14446
|
+
}
|
|
14148
14447
|
function claudeConfigDir() {
|
|
14149
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
14448
|
+
return process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude");
|
|
14150
14449
|
}
|
|
14151
14450
|
function sessionLogFor(file, configDir = claudeConfigDir()) {
|
|
14152
14451
|
const tasks = dirname4(file);
|
|
14153
14452
|
if (basename2(tasks) !== "tasks")
|
|
14154
14453
|
return null;
|
|
14155
14454
|
const session = dirname4(tasks);
|
|
14156
|
-
const path =
|
|
14455
|
+
const path = join8(configDir, "projects", basename2(dirname4(session)), `${basename2(session)}.jsonl`);
|
|
14157
14456
|
return existsSync8(path) ? path : null;
|
|
14158
14457
|
}
|
|
14159
14458
|
function taskEnd(text, toolId) {
|
|
@@ -14466,7 +14765,8 @@ function spawnOf(meta) {
|
|
|
14466
14765
|
const claude = asRecord(asRecord(meta).claudeCode);
|
|
14467
14766
|
const parentToolId = typeof claude.parentToolUseId === "string" ? claude.parentToolUseId : undefined;
|
|
14468
14767
|
const codex = asRecord(asRecord(meta).codex).subagent;
|
|
14469
|
-
const
|
|
14768
|
+
const spawns = claude.toolName === "Agent" || claude.toolName === "Task";
|
|
14769
|
+
const subagent = spawns || claude.subagent === true || Object.keys(asRecord(codex)).length > 0;
|
|
14470
14770
|
return { ...parentToolId ? { parentToolId } : {}, ...subagent ? { subagent: true } : {} };
|
|
14471
14771
|
}
|
|
14472
14772
|
function asRecord(v) {
|
|
@@ -14513,11 +14813,26 @@ function endOfAsk(req, res, memos) {
|
|
|
14513
14813
|
isError: !allowed
|
|
14514
14814
|
};
|
|
14515
14815
|
}
|
|
14516
|
-
function
|
|
14816
|
+
function release(toolId, memo) {
|
|
14817
|
+
const held = memo.held;
|
|
14818
|
+
if (!held)
|
|
14819
|
+
return null;
|
|
14820
|
+
delete memo.held;
|
|
14821
|
+
if (!emptyInput(memo.input))
|
|
14822
|
+
memo.writing = false;
|
|
14823
|
+
return memo.ended ? null : { type: "tool-update", toolId, ...held };
|
|
14824
|
+
}
|
|
14825
|
+
function abandoned(memos, parent, opening = false) {
|
|
14517
14826
|
const out = [];
|
|
14518
14827
|
for (const [toolId, memo] of memos) {
|
|
14519
14828
|
if (memo.ended || !memo.writing || memo.parent !== parent)
|
|
14520
14829
|
continue;
|
|
14830
|
+
if (opening && memo.held) {
|
|
14831
|
+
const sent = release(toolId, memo);
|
|
14832
|
+
if (sent)
|
|
14833
|
+
out.push(sent);
|
|
14834
|
+
continue;
|
|
14835
|
+
}
|
|
14521
14836
|
memo.ended = true;
|
|
14522
14837
|
out.push({ type: "tool-end", toolId });
|
|
14523
14838
|
}
|
|
@@ -14546,7 +14861,7 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14546
14861
|
}
|
|
14547
14862
|
case "tool_call": {
|
|
14548
14863
|
const spawn = spawnOf(update._meta);
|
|
14549
|
-
const out = abandoned(memos, spawn.parentToolId);
|
|
14864
|
+
const out = abandoned(memos, spawn.parentToolId, true);
|
|
14550
14865
|
const input = update.rawInput ?? { locations: update.locations ?? [] };
|
|
14551
14866
|
const head = heading(update);
|
|
14552
14867
|
const memo = {
|
|
@@ -14556,7 +14871,8 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14556
14871
|
rawOutput: update.rawOutput,
|
|
14557
14872
|
ended: false,
|
|
14558
14873
|
writing: isWrittenKind(head.kind) && emptyInput(input),
|
|
14559
|
-
...spawn.parentToolId ? { parent: spawn.parentToolId } : {}
|
|
14874
|
+
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
14875
|
+
...spawn.subagent ? { spawns: true } : {}
|
|
14560
14876
|
};
|
|
14561
14877
|
memos.set(update.toolCallId, memo);
|
|
14562
14878
|
out.push({
|
|
@@ -14586,7 +14902,8 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14586
14902
|
content: [],
|
|
14587
14903
|
ended: false,
|
|
14588
14904
|
writing: isWrittenKind(update.kind ?? undefined) && emptyInput(input),
|
|
14589
|
-
...spawn.parentToolId ? { parent: spawn.parentToolId } : {}
|
|
14905
|
+
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
14906
|
+
...spawn.subagent ? { spawns: true } : {}
|
|
14590
14907
|
};
|
|
14591
14908
|
memos.set(update.toolCallId, memo);
|
|
14592
14909
|
out.push({
|
|
@@ -14599,26 +14916,27 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14599
14916
|
...spawn
|
|
14600
14917
|
});
|
|
14601
14918
|
}
|
|
14602
|
-
|
|
14603
|
-
return out;
|
|
14604
|
-
const refined = { type: "tool-update", toolId: update.toolCallId };
|
|
14919
|
+
const refined = { ...memo.held };
|
|
14605
14920
|
const title = memo.name === TOOL_SEARCH ? toolSearchQuery(update.rawInput) : update.title;
|
|
14606
|
-
if (title && title !== memo.title)
|
|
14607
|
-
memo.title = title;
|
|
14608
|
-
|
|
14609
|
-
}
|
|
14610
|
-
if (update.name && update.name !== memo.name) {
|
|
14921
|
+
if (title && title !== memo.title)
|
|
14922
|
+
refined.title = memo.title = title;
|
|
14923
|
+
if (update.name && update.name !== memo.name)
|
|
14611
14924
|
refined.name = memo.name = update.name;
|
|
14612
|
-
}
|
|
14613
14925
|
if (update.kind && update.kind !== memo.kind)
|
|
14614
14926
|
refined.kind = memo.kind = update.kind;
|
|
14615
|
-
if (update.rawInput !== undefined)
|
|
14927
|
+
if (update.rawInput !== undefined)
|
|
14616
14928
|
refined.input = memo.input = update.rawInput;
|
|
14617
|
-
|
|
14618
|
-
|
|
14929
|
+
if (update.rawInput !== undefined && update.content === undefined && update.status === undefined) {
|
|
14930
|
+
if (out.length === 0)
|
|
14931
|
+
memo.held = refined;
|
|
14932
|
+
return out;
|
|
14933
|
+
}
|
|
14934
|
+
delete memo.held;
|
|
14935
|
+
if (!emptyInput(memo.input))
|
|
14936
|
+
memo.writing = false;
|
|
14937
|
+
if (Object.keys(refined).length > 0 && !memo.ended && out.length === 0) {
|
|
14938
|
+
out.push({ type: "tool-update", toolId: update.toolCallId, ...refined });
|
|
14619
14939
|
}
|
|
14620
|
-
if (Object.keys(refined).length > 2 && !memo.ended && out.length === 0)
|
|
14621
|
-
out.push(refined);
|
|
14622
14940
|
if (update.content)
|
|
14623
14941
|
memo.content = [...memo.content, ...update.content];
|
|
14624
14942
|
if (update.rawOutput !== undefined)
|
|
@@ -14664,14 +14982,23 @@ function endOf(toolId, memo, status, sink, background) {
|
|
|
14664
14982
|
return { type: "tool-update", toolId, background: true };
|
|
14665
14983
|
}
|
|
14666
14984
|
const images = sink ? toolImages(memo.content, sink) : [];
|
|
14985
|
+
const detached = status === "completed" && !!memo.spawns && spawnDetached(wholeText(memo.content, memo.rawOutput));
|
|
14667
14986
|
return {
|
|
14668
14987
|
type: "tool-end",
|
|
14669
14988
|
toolId,
|
|
14670
14989
|
output,
|
|
14671
14990
|
isError: status === "failed" || exitCodeOf(memo.rawOutput) > 0,
|
|
14991
|
+
...detached ? { detached: true } : {},
|
|
14672
14992
|
...images.length ? { images } : {}
|
|
14673
14993
|
};
|
|
14674
14994
|
}
|
|
14995
|
+
function wholeText(content, rawOutput) {
|
|
14996
|
+
const parts = content.flatMap((c) => c.type === "content" && c.content.type === "text" ? [c.content.text] : []);
|
|
14997
|
+
if (typeof rawOutput === "string")
|
|
14998
|
+
parts.push(rawOutput);
|
|
14999
|
+
return parts.join(`
|
|
15000
|
+
`);
|
|
15001
|
+
}
|
|
14675
15002
|
function toolImages(content, sink) {
|
|
14676
15003
|
const out = [];
|
|
14677
15004
|
for (const c of content) {
|
|
@@ -14757,6 +15084,11 @@ function steerOutcome(value) {
|
|
|
14757
15084
|
// ../daemon/src/agent/acp/session.ts
|
|
14758
15085
|
var DEFAULT_IDLE_MS = Number(process.env.TOYON_AGENT_IDLE_MS) || 5 * 60000;
|
|
14759
15086
|
var OWN_SETTLE_MS = 1e4;
|
|
15087
|
+
var INPUT_HOLD_MS = 1000;
|
|
15088
|
+
var queued = (q) => ({
|
|
15089
|
+
text: q.text,
|
|
15090
|
+
...q.attachments ? { attachments: q.attachments } : {}
|
|
15091
|
+
});
|
|
14760
15092
|
function recorded(reply) {
|
|
14761
15093
|
if (reply?.kind === "choice")
|
|
14762
15094
|
return { choiceId: reply.choiceId };
|
|
@@ -14872,7 +15204,7 @@ class AcpSession {
|
|
|
14872
15204
|
return this.queue.length > 0 || this.steered.length > 0 || this.refused !== null || this.asks.size > 0;
|
|
14873
15205
|
}
|
|
14874
15206
|
get queueItems() {
|
|
14875
|
-
return this.waiting().map(
|
|
15207
|
+
return this.waiting().map(queued);
|
|
14876
15208
|
}
|
|
14877
15209
|
waiting() {
|
|
14878
15210
|
return this.queue.filter((q) => !q.recorded);
|
|
@@ -14881,13 +15213,19 @@ class AcpSession {
|
|
|
14881
15213
|
queueChanged() {
|
|
14882
15214
|
this.onQueueChange?.();
|
|
14883
15215
|
}
|
|
14884
|
-
unqueue(index) {
|
|
15216
|
+
unqueue(index, taken) {
|
|
14885
15217
|
const item = this.waiting()[index];
|
|
14886
15218
|
if (!item)
|
|
14887
15219
|
return;
|
|
14888
15220
|
this.queue.splice(this.queue.indexOf(item), 1);
|
|
15221
|
+
taken?.(queued(item));
|
|
15222
|
+
this.letGo(item);
|
|
14889
15223
|
this.queueChanged();
|
|
14890
15224
|
}
|
|
15225
|
+
letGo(item) {
|
|
15226
|
+
if (!item.recorded)
|
|
15227
|
+
this.d.attachments.uploads.release(uploadIds(item.attachments));
|
|
15228
|
+
}
|
|
14891
15229
|
transcript() {
|
|
14892
15230
|
return this.log.entries;
|
|
14893
15231
|
}
|
|
@@ -14905,8 +15243,14 @@ class AcpSession {
|
|
|
14905
15243
|
send(text, opts = {}) {
|
|
14906
15244
|
if (this.stopped)
|
|
14907
15245
|
return log.warn(this.d.worktreeId, "send after close dropped");
|
|
15246
|
+
for (const ask of [...this.asks.values()])
|
|
15247
|
+
ask.settle("skipped");
|
|
15248
|
+
this.post(text, opts);
|
|
15249
|
+
}
|
|
15250
|
+
post(text, opts = {}) {
|
|
14908
15251
|
const { context, attachments } = opts;
|
|
14909
15252
|
const item = { text, context, ...attachments?.length ? { attachments } : {} };
|
|
15253
|
+
this.d.attachments.uploads.hold(uploadIds(attachments));
|
|
14910
15254
|
if ((this.running || this.own) && !this.interrupted && !this.held && this.live?.conn.steering) {
|
|
14911
15255
|
return fireAndForget(this.d.worktreeId, this.steer(item), "agent steer");
|
|
14912
15256
|
}
|
|
@@ -14993,6 +15337,8 @@ class AcpSession {
|
|
|
14993
15337
|
async close() {
|
|
14994
15338
|
this.stopped = true;
|
|
14995
15339
|
this.clearReaper();
|
|
15340
|
+
for (const item of this.queue)
|
|
15341
|
+
this.letGo(item);
|
|
14996
15342
|
this.queue = [];
|
|
14997
15343
|
this.steered = [];
|
|
14998
15344
|
this.queueChanged();
|
|
@@ -15116,8 +15462,8 @@ class AcpSession {
|
|
|
15116
15462
|
retry() {
|
|
15117
15463
|
const item = this.refused;
|
|
15118
15464
|
this.refused = null;
|
|
15119
|
-
if (item)
|
|
15120
|
-
this.
|
|
15465
|
+
if (item && !this.stopped)
|
|
15466
|
+
this.enqueue(item);
|
|
15121
15467
|
}
|
|
15122
15468
|
planWithheld(conn, methodId) {
|
|
15123
15469
|
return this.d.planSignIn?.() === false && conn.spec.terminalLogins?.[methodId]?.plan === true;
|
|
@@ -15140,8 +15486,12 @@ class AcpSession {
|
|
|
15140
15486
|
}
|
|
15141
15487
|
async record({ text, attachments }) {
|
|
15142
15488
|
const stored = [];
|
|
15143
|
-
|
|
15144
|
-
|
|
15489
|
+
try {
|
|
15490
|
+
for (const a of attachments ?? [])
|
|
15491
|
+
stored.push(await this.d.attachments.put(this.d.worktreeId, this.seq[a.kind]++, a));
|
|
15492
|
+
} finally {
|
|
15493
|
+
this.d.attachments.uploads.release(uploadIds(attachments));
|
|
15494
|
+
}
|
|
15145
15495
|
this.emit({
|
|
15146
15496
|
type: "user-message",
|
|
15147
15497
|
text,
|
|
@@ -15163,6 +15513,11 @@ class AcpSession {
|
|
|
15163
15513
|
}
|
|
15164
15514
|
async runTurn(item) {
|
|
15165
15515
|
item.recorded ??= await this.record(item);
|
|
15516
|
+
try {
|
|
15517
|
+
await this.d.beforeTurn?.();
|
|
15518
|
+
} catch (e) {
|
|
15519
|
+
log.warn(this.d.worktreeId, "before-turn hook failed", e);
|
|
15520
|
+
}
|
|
15166
15521
|
this.emit({ type: "turn-start", ts: Date.now() });
|
|
15167
15522
|
const live = await this.ensureLive("a turn");
|
|
15168
15523
|
await this.applyMode(live);
|
|
@@ -15267,7 +15622,8 @@ class AcpSession {
|
|
|
15267
15622
|
sessionId,
|
|
15268
15623
|
cwd: this.d.cwd,
|
|
15269
15624
|
mcpServers: [],
|
|
15270
|
-
additionalDirectories
|
|
15625
|
+
additionalDirectories,
|
|
15626
|
+
...conn.spec.systemPrompt === "meta-append" ? { _meta: { systemPrompt: { append: SYSTEM_APPEND } } } : {}
|
|
15271
15627
|
});
|
|
15272
15628
|
modes = r.modes;
|
|
15273
15629
|
configOptions = r.configOptions;
|
|
@@ -15390,6 +15746,10 @@ class AcpSession {
|
|
|
15390
15746
|
if (params.update.sessionUpdate === "config_option_update")
|
|
15391
15747
|
this.absorb(live, params.update.configOptions);
|
|
15392
15748
|
const events = mapUpdate(params.update, live.tools, this.d.worktreeId, (file, bytes) => fireAndForget(this.d.worktreeId, this.d.attachments.putToolImage(this.d.worktreeId, file, bytes), "tool image"), (toolId, start, input) => this.tasks.start(toolId, start, input));
|
|
15749
|
+
this.forward(live, events);
|
|
15750
|
+
this.holdInput(live, params.update);
|
|
15751
|
+
}
|
|
15752
|
+
forward(live, events) {
|
|
15393
15753
|
if (!this.running)
|
|
15394
15754
|
this.trackOwn(events);
|
|
15395
15755
|
for (const ev of events) {
|
|
@@ -15398,6 +15758,23 @@ class AcpSession {
|
|
|
15398
15758
|
this.emit(ev.type === "session-info" ? { ...ev, sessionId: live.sessionId } : ev);
|
|
15399
15759
|
}
|
|
15400
15760
|
}
|
|
15761
|
+
holdInput(live, update) {
|
|
15762
|
+
if (update.sessionUpdate !== "tool_call_update")
|
|
15763
|
+
return;
|
|
15764
|
+
const toolId = update.toolCallId;
|
|
15765
|
+
const memo = live.tools.get(toolId);
|
|
15766
|
+
const held = memo?.held;
|
|
15767
|
+
if (!memo || !held)
|
|
15768
|
+
return;
|
|
15769
|
+
const t = setTimeout(() => {
|
|
15770
|
+
if (this.live !== live || memo.held !== held || !(this.running || this.own))
|
|
15771
|
+
return;
|
|
15772
|
+
const sent = release(toolId, memo);
|
|
15773
|
+
if (sent)
|
|
15774
|
+
this.forward(live, [sent]);
|
|
15775
|
+
}, this.d.inputHoldMs ?? INPUT_HOLD_MS);
|
|
15776
|
+
t.unref?.();
|
|
15777
|
+
}
|
|
15401
15778
|
onPermission(params, bounds, sandboxed) {
|
|
15402
15779
|
const tools = this.live?.tools;
|
|
15403
15780
|
return Promise.resolve(this.answerPermission(params, bounds, sandboxed)).then((res) => {
|
|
@@ -15456,7 +15833,7 @@ class AcpSession {
|
|
|
15456
15833
|
async sayPlanEdited(proposed, path) {
|
|
15457
15834
|
if (!await this.d.planEdited?.(path, proposed))
|
|
15458
15835
|
return;
|
|
15459
|
-
this.
|
|
15836
|
+
this.post(`I edited the plan before approving it. Build what is in ${path}, not the plan you proposed.`);
|
|
15460
15837
|
}
|
|
15461
15838
|
onElicit(params, signal) {
|
|
15462
15839
|
const live = this.live;
|
|
@@ -15651,9 +16028,9 @@ function sameCommands(a, b) {
|
|
|
15651
16028
|
}
|
|
15652
16029
|
|
|
15653
16030
|
// ../daemon/src/agent/planDoc.ts
|
|
15654
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
15655
|
-
import { join as
|
|
15656
|
-
var PLANS_DIR =
|
|
16031
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
16032
|
+
import { join as join9 } from "path";
|
|
16033
|
+
var PLANS_DIR = join9(".toyon", "plans");
|
|
15657
16034
|
var PLAN_NAME = /^(\d+)\.md$/;
|
|
15658
16035
|
function isPlanPath(rel) {
|
|
15659
16036
|
return rel.startsWith(`${PLANS_DIR}/`) && PLAN_NAME.test(rel.slice(PLANS_DIR.length + 1));
|
|
@@ -15662,7 +16039,7 @@ function nextNumber(dir) {
|
|
|
15662
16039
|
if (!existsSync9(dir))
|
|
15663
16040
|
return 1;
|
|
15664
16041
|
let max = 0;
|
|
15665
|
-
for (const name of
|
|
16042
|
+
for (const name of readdirSync3(dir)) {
|
|
15666
16043
|
const n = Number(PLAN_NAME.exec(name)?.[1]);
|
|
15667
16044
|
if (n > max)
|
|
15668
16045
|
max = n;
|
|
@@ -15670,11 +16047,11 @@ function nextNumber(dir) {
|
|
|
15670
16047
|
return max + 1;
|
|
15671
16048
|
}
|
|
15672
16049
|
async function writePlanDoc(cwd, markdown) {
|
|
15673
|
-
const dir =
|
|
15674
|
-
const rel =
|
|
16050
|
+
const dir = join9(cwd, PLANS_DIR);
|
|
16051
|
+
const rel = join9(PLANS_DIR, `${nextNumber(dir)}.md`);
|
|
15675
16052
|
try {
|
|
15676
|
-
|
|
15677
|
-
writeFileSync4(
|
|
16053
|
+
mkdirSync6(dir, { recursive: true });
|
|
16054
|
+
writeFileSync4(join9(cwd, rel), markdown.endsWith(`
|
|
15678
16055
|
`) ? markdown : `${markdown}
|
|
15679
16056
|
`);
|
|
15680
16057
|
} catch (e) {
|
|
@@ -15685,7 +16062,7 @@ async function writePlanDoc(cwd, markdown) {
|
|
|
15685
16062
|
return rel;
|
|
15686
16063
|
}
|
|
15687
16064
|
function planEdited(cwd, rel, proposed) {
|
|
15688
|
-
const file =
|
|
16065
|
+
const file = join9(cwd, rel);
|
|
15689
16066
|
if (!existsSync9(file))
|
|
15690
16067
|
return false;
|
|
15691
16068
|
try {
|
|
@@ -16990,11 +17367,12 @@ function defaultAgent(wt, d, preview, onProcess) {
|
|
|
16990
17367
|
launch: (spec) => d.agents.command(spec),
|
|
16991
17368
|
planSignIn: () => d.managed?.planSignIn ?? true,
|
|
16992
17369
|
transcriptsDir: d.paths.transcriptsDir,
|
|
16993
|
-
attachments: d.attachments ?? new AttachmentStore(d.paths.attachmentsDir),
|
|
17370
|
+
attachments: d.attachments ?? new AttachmentStore(d.paths.attachmentsDir, new UploadStore(d.paths.uploadsDir)),
|
|
16994
17371
|
getSessionId: () => d.state.session(wt.id),
|
|
16995
17372
|
setSessionId: (id) => d.state.setSession(wt.id, id),
|
|
16996
17373
|
onEvent: (event, seq) => d.hub.emit("agent", wt.id, seq, event),
|
|
16997
17374
|
onStatus: (status) => d.hub.emit("agentStatus", wt.id, status),
|
|
17375
|
+
beforeTurn: async () => d.turnStarting?.(wt.id),
|
|
16998
17376
|
onAuth: (agentId, o) => d.accounts?.observe(agentId, o),
|
|
16999
17377
|
seedCommands: () => d.state.cachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId),
|
|
17000
17378
|
onCommandsLearned: (commands) => d.state.setCachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId, commands),
|
|
@@ -17298,9 +17676,12 @@ class RuntimeRegistry {
|
|
|
17298
17676
|
return rt;
|
|
17299
17677
|
}
|
|
17300
17678
|
async start(wt, repo) {
|
|
17301
|
-
|
|
17679
|
+
const record = this.deps.state.worktree(wt.id);
|
|
17680
|
+
if (!record)
|
|
17302
17681
|
return;
|
|
17303
17682
|
const rt = this.ensureAgent(wt);
|
|
17683
|
+
if (record.parked)
|
|
17684
|
+
return;
|
|
17304
17685
|
const run = repo.needsSetup ? EMPTY_RUN : resolveRun(repo, wt);
|
|
17305
17686
|
const leads = wt.kind !== "main" || !this.deps.mainLeads || this.deps.mainLeads(wt.repoId);
|
|
17306
17687
|
if (rt.procs) {
|
|
@@ -17699,6 +18080,11 @@ class RuntimeRegistry {
|
|
|
17699
18080
|
return rt?.procs ? previewTargetOf(rt.procs, rt.previewName) : null;
|
|
17700
18081
|
}
|
|
17701
18082
|
previewStanding(id) {
|
|
18083
|
+
const wt = this.deps.state.worktree(id);
|
|
18084
|
+
if (wt?.parked) {
|
|
18085
|
+
const repo = this.deps.state.repos.find((r) => r.id === wt.repoId);
|
|
18086
|
+
return repo && !repo.needsSetup && resolveRun(repo, wt).preview ? { status: "parked" } : null;
|
|
18087
|
+
}
|
|
17702
18088
|
if (this.settingUp.has(id))
|
|
17703
18089
|
return { status: "setup" };
|
|
17704
18090
|
const rt = this.runtimes.get(id);
|
|
@@ -17763,17 +18149,17 @@ class RuntimeRegistry {
|
|
|
17763
18149
|
}
|
|
17764
18150
|
|
|
17765
18151
|
// ../daemon/src/worktrees/naming.ts
|
|
17766
|
-
import { randomBytes } from "crypto";
|
|
18152
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
17767
18153
|
import { existsSync as existsSync11 } from "fs";
|
|
17768
|
-
import { join as
|
|
18154
|
+
import { join as join10 } from "path";
|
|
17769
18155
|
function shortId() {
|
|
17770
|
-
return
|
|
18156
|
+
return randomBytes2(5).toString("hex");
|
|
17771
18157
|
}
|
|
17772
18158
|
function freeSlot(parent) {
|
|
17773
18159
|
for (;; ) {
|
|
17774
18160
|
const id = shortId();
|
|
17775
18161
|
const dir = `wt-${id.slice(0, 4)}`;
|
|
17776
|
-
if (!existsSync11(
|
|
18162
|
+
if (!existsSync11(join10(parent, dir)))
|
|
17777
18163
|
return { id, dir };
|
|
17778
18164
|
}
|
|
17779
18165
|
}
|
|
@@ -18113,6 +18499,8 @@ function attachmentText(a) {
|
|
|
18113
18499
|
return a.text;
|
|
18114
18500
|
if (a.kind === "image")
|
|
18115
18501
|
return a.name ? `image ${a.name}` : "an image";
|
|
18502
|
+
if (a.kind === "file")
|
|
18503
|
+
return `file ${a.name}`;
|
|
18116
18504
|
return `element <${a.component ?? a.tag}>${a.text ? ` "${a.text}"` : ""}`;
|
|
18117
18505
|
}
|
|
18118
18506
|
function parseName(text) {
|
|
@@ -18187,29 +18575,29 @@ function makePlanner(agents) {
|
|
|
18187
18575
|
}
|
|
18188
18576
|
|
|
18189
18577
|
// ../daemon/src/core/assets.ts
|
|
18190
|
-
import { existsSync as existsSync12, readdirSync as
|
|
18191
|
-
import { join as
|
|
18578
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, rmSync as rmSync2, statSync as statSync4 } from "fs";
|
|
18579
|
+
import { join as join11, resolve as resolve5 } from "path";
|
|
18192
18580
|
function locateAssets(here, env = process.env) {
|
|
18193
|
-
const packaged = existsSync12(
|
|
18581
|
+
const packaged = existsSync12(join11(here, "shell", "index.html"));
|
|
18194
18582
|
return {
|
|
18195
|
-
shellDist: env.TOYON_SHELL_DIST ?? (packaged ?
|
|
18196
|
-
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ?
|
|
18583
|
+
shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join11(here, "shell") : join11(here, "../../shell/dist")),
|
|
18584
|
+
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join11(here, "bridge.js") : join11(here, "../../bridge/dist/bridge.js")),
|
|
18197
18585
|
sourceRoot: packaged ? null : resolve5(here, "../../.."),
|
|
18198
|
-
packageJson: packaged ?
|
|
18586
|
+
packageJson: packaged ? join11(here, "..", "package.json") : null
|
|
18199
18587
|
};
|
|
18200
18588
|
}
|
|
18201
18589
|
var KEEP_MS = 3 * 24 * 60 * 60 * 1000;
|
|
18202
18590
|
function pruneAssets(shellDist) {
|
|
18203
|
-
const dir =
|
|
18591
|
+
const dir = join11(shellDist, "assets");
|
|
18204
18592
|
let names;
|
|
18205
18593
|
try {
|
|
18206
|
-
names =
|
|
18594
|
+
names = readdirSync4(dir);
|
|
18207
18595
|
} catch {
|
|
18208
18596
|
return 0;
|
|
18209
18597
|
}
|
|
18210
18598
|
const found = [];
|
|
18211
18599
|
for (const name of names) {
|
|
18212
|
-
const path =
|
|
18600
|
+
const path = join11(dir, name);
|
|
18213
18601
|
try {
|
|
18214
18602
|
found.push({ path, mtime: statSync4(path).mtimeMs });
|
|
18215
18603
|
} catch {}
|
|
@@ -18222,7 +18610,7 @@ function pruneAssets(shellDist) {
|
|
|
18222
18610
|
if (newest - f.mtime <= KEEP_MS)
|
|
18223
18611
|
continue;
|
|
18224
18612
|
try {
|
|
18225
|
-
|
|
18613
|
+
rmSync2(f.path, { recursive: true, force: true });
|
|
18226
18614
|
gone++;
|
|
18227
18615
|
} catch {}
|
|
18228
18616
|
}
|
|
@@ -18874,7 +19262,7 @@ class SelfWatch {
|
|
|
18874
19262
|
}
|
|
18875
19263
|
|
|
18876
19264
|
// ../daemon/src/core/state.ts
|
|
18877
|
-
import { randomBytes as
|
|
19265
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
18878
19266
|
import { existsSync as existsSync14, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
18879
19267
|
var GROUPS_SAVE_MS = 250;
|
|
18880
19268
|
var SEEN_WORKTREES = 50;
|
|
@@ -18936,7 +19324,7 @@ function loadOrCreateToken(paths) {
|
|
|
18936
19324
|
const TOKEN_FILE = paths.tokenFile;
|
|
18937
19325
|
if (existsSync14(TOKEN_FILE))
|
|
18938
19326
|
return readFileSync10(TOKEN_FILE, "utf8").trim();
|
|
18939
|
-
const token =
|
|
19327
|
+
const token = randomBytes3(32).toString("hex");
|
|
18940
19328
|
writeFileSync5(TOKEN_FILE, token, { mode: 384 });
|
|
18941
19329
|
return token;
|
|
18942
19330
|
}
|
|
@@ -19150,7 +19538,7 @@ class StateStore {
|
|
|
19150
19538
|
}
|
|
19151
19539
|
|
|
19152
19540
|
// ../daemon/src/design/service.ts
|
|
19153
|
-
import { join as
|
|
19541
|
+
import { join as join12 } from "path";
|
|
19154
19542
|
|
|
19155
19543
|
// ../daemon/src/design/scan.ts
|
|
19156
19544
|
var COLOR = /^(#|rgba?\(|hsla?\(|oklch\(|oklab\(|lab\(|lch\(|color\(|color-mix\(|light-dark\()/i;
|
|
@@ -19600,7 +19988,7 @@ class DesignService {
|
|
|
19600
19988
|
async function readAll(root, paths) {
|
|
19601
19989
|
const out = [];
|
|
19602
19990
|
for (const path of paths) {
|
|
19603
|
-
const file = Bun.file(
|
|
19991
|
+
const file = Bun.file(join12(root, path));
|
|
19604
19992
|
if (file.size === 0 || file.size > MAX_FILE_BYTES)
|
|
19605
19993
|
continue;
|
|
19606
19994
|
try {
|
|
@@ -19715,7 +20103,7 @@ function typicalUse(classes) {
|
|
|
19715
20103
|
var normal = (s) => s.replace(/[-_]/g, "").toLowerCase();
|
|
19716
20104
|
async function loadProjectTypescript(root) {
|
|
19717
20105
|
try {
|
|
19718
|
-
return await import(
|
|
20106
|
+
return await import(join12(root, "node_modules", "typescript", "lib", "typescript.js"));
|
|
19719
20107
|
} catch {
|
|
19720
20108
|
return null;
|
|
19721
20109
|
}
|
|
@@ -19728,28 +20116,32 @@ var SAVE_DELAY_MS = 1000;
|
|
|
19728
20116
|
class DraftStore {
|
|
19729
20117
|
d;
|
|
19730
20118
|
drafts = {};
|
|
20119
|
+
lists = {};
|
|
20120
|
+
dirty = { text: false, lists: false };
|
|
19731
20121
|
saveTimer = null;
|
|
19732
20122
|
constructor(d) {
|
|
19733
20123
|
this.d = d;
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
19738
|
-
|
|
19739
|
-
|
|
19740
|
-
|
|
19741
|
-
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
|
|
19745
|
-
log.warn("drafts", `skipped an unreadable ${d.file}`, e);
|
|
20124
|
+
for (const [id, text] of readRecord(d.file)) {
|
|
20125
|
+
if (typeof text === "string" && text)
|
|
20126
|
+
this.drafts[id] = text;
|
|
20127
|
+
}
|
|
20128
|
+
for (const [id, list] of readRecord(d.attachmentsFile)) {
|
|
20129
|
+
const items = (Array.isArray(list) ? list : []).flatMap((item) => {
|
|
20130
|
+
const r = attachmentInputSchema.safeParse(item);
|
|
20131
|
+
return r.success ? [r.data] : [];
|
|
20132
|
+
});
|
|
20133
|
+
if (items.length)
|
|
20134
|
+
this.lists[id] = items;
|
|
19746
20135
|
}
|
|
19747
20136
|
}
|
|
19748
20137
|
all() {
|
|
19749
20138
|
return { ...this.drafts };
|
|
19750
20139
|
}
|
|
20140
|
+
allAttachments() {
|
|
20141
|
+
return { ...this.lists };
|
|
20142
|
+
}
|
|
19751
20143
|
has(boxId) {
|
|
19752
|
-
return !!this.drafts[boxId]?.trim();
|
|
20144
|
+
return !!this.drafts[boxId]?.trim() || !!this.lists[boxId]?.length;
|
|
19753
20145
|
}
|
|
19754
20146
|
text(boxId) {
|
|
19755
20147
|
return this.drafts[boxId] ?? "";
|
|
@@ -19761,22 +20153,67 @@ class DraftStore {
|
|
|
19761
20153
|
this.drafts[boxId] = text;
|
|
19762
20154
|
else
|
|
19763
20155
|
delete this.drafts[boxId];
|
|
19764
|
-
this.scheduleSave();
|
|
20156
|
+
this.scheduleSave("text");
|
|
19765
20157
|
this.d.hub.emit("draftChanged", boxId, text, clientId);
|
|
19766
20158
|
}
|
|
20159
|
+
attachments(boxId) {
|
|
20160
|
+
return this.lists[boxId] ?? [];
|
|
20161
|
+
}
|
|
20162
|
+
setAttachments(boxId, items, clientId) {
|
|
20163
|
+
if (JSON.stringify(this.lists[boxId] ?? []) === JSON.stringify(items))
|
|
20164
|
+
return;
|
|
20165
|
+
if (items.length)
|
|
20166
|
+
this.lists[boxId] = items;
|
|
20167
|
+
else
|
|
20168
|
+
delete this.lists[boxId];
|
|
20169
|
+
this.scheduleSave("lists");
|
|
20170
|
+
this.d.uploads?.keep(this.uploadIds());
|
|
20171
|
+
this.d.hub.emit("attachmentsChanged", boxId, items, clientId);
|
|
20172
|
+
}
|
|
20173
|
+
take(boxId, clientId) {
|
|
20174
|
+
const was = { text: this.text(boxId), items: this.attachments(boxId) };
|
|
20175
|
+
this.set(boxId, "", clientId);
|
|
20176
|
+
this.setAttachments(boxId, [], clientId);
|
|
20177
|
+
return was;
|
|
20178
|
+
}
|
|
20179
|
+
putBack(boxId, was, clientId) {
|
|
20180
|
+
if (was.text && !this.text(boxId))
|
|
20181
|
+
this.set(boxId, was.text, clientId);
|
|
20182
|
+
if (was.items.length)
|
|
20183
|
+
this.setAttachments(boxId, [...was.items, ...this.attachments(boxId)], clientId);
|
|
20184
|
+
}
|
|
20185
|
+
uploadIds() {
|
|
20186
|
+
return Object.values(this.lists).flatMap((items) => uploadIds(items));
|
|
20187
|
+
}
|
|
19767
20188
|
drop(boxId) {
|
|
19768
20189
|
this.set(boxId, "");
|
|
20190
|
+
this.setAttachments(boxId, []);
|
|
20191
|
+
}
|
|
20192
|
+
move(fromId, toId, over = false) {
|
|
20193
|
+
const text = this.text(fromId);
|
|
20194
|
+
const items = this.attachments(fromId);
|
|
20195
|
+
if (!text && !items.length)
|
|
20196
|
+
return false;
|
|
20197
|
+
if (!over && (this.text(toId) || this.attachments(toId).length))
|
|
20198
|
+
return false;
|
|
20199
|
+
this.set(toId, text);
|
|
20200
|
+
this.setAttachments(toId, items);
|
|
20201
|
+
this.drop(fromId);
|
|
20202
|
+
return true;
|
|
19769
20203
|
}
|
|
19770
20204
|
prune(known) {
|
|
19771
|
-
let changed = false;
|
|
19772
20205
|
for (const id of Object.keys(this.drafts)) {
|
|
19773
20206
|
if (known(id))
|
|
19774
20207
|
continue;
|
|
19775
20208
|
delete this.drafts[id];
|
|
19776
|
-
|
|
20209
|
+
this.scheduleSave("text");
|
|
20210
|
+
}
|
|
20211
|
+
for (const id of Object.keys(this.lists)) {
|
|
20212
|
+
if (known(id))
|
|
20213
|
+
continue;
|
|
20214
|
+
delete this.lists[id];
|
|
20215
|
+
this.scheduleSave("lists");
|
|
19777
20216
|
}
|
|
19778
|
-
if (changed)
|
|
19779
|
-
this.scheduleSave();
|
|
19780
20217
|
}
|
|
19781
20218
|
flush() {
|
|
19782
20219
|
if (!this.saveTimer)
|
|
@@ -19785,7 +20222,8 @@ class DraftStore {
|
|
|
19785
20222
|
this.saveTimer = null;
|
|
19786
20223
|
this.write();
|
|
19787
20224
|
}
|
|
19788
|
-
scheduleSave() {
|
|
20225
|
+
scheduleSave(which) {
|
|
20226
|
+
this.dirty[which] = true;
|
|
19789
20227
|
if (this.saveTimer)
|
|
19790
20228
|
return;
|
|
19791
20229
|
this.saveTimer = setTimeout(() => {
|
|
@@ -19795,13 +20233,31 @@ class DraftStore {
|
|
|
19795
20233
|
this.saveTimer.unref?.();
|
|
19796
20234
|
}
|
|
19797
20235
|
write() {
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
20236
|
+
if (this.dirty.text)
|
|
20237
|
+
writeAtomic(this.d.file, this.drafts);
|
|
20238
|
+
if (this.dirty.lists)
|
|
20239
|
+
writeAtomic(this.d.attachmentsFile, this.lists);
|
|
20240
|
+
this.dirty = { text: false, lists: false };
|
|
20241
|
+
}
|
|
20242
|
+
}
|
|
20243
|
+
function readRecord(file) {
|
|
20244
|
+
if (!existsSync15(file))
|
|
20245
|
+
return [];
|
|
20246
|
+
try {
|
|
20247
|
+
const raw = JSON.parse(readFileSync11(file, "utf8"));
|
|
20248
|
+
return raw && typeof raw === "object" ? Object.entries(raw) : [];
|
|
20249
|
+
} catch (e) {
|
|
20250
|
+
log.warn("drafts", `skipped an unreadable ${file}`, e);
|
|
20251
|
+
return [];
|
|
20252
|
+
}
|
|
20253
|
+
}
|
|
20254
|
+
function writeAtomic(file, value) {
|
|
20255
|
+
const tmp = `${file}.tmp`;
|
|
20256
|
+
try {
|
|
20257
|
+
writeFileSync6(tmp, JSON.stringify(value));
|
|
20258
|
+
renameSync3(tmp, file);
|
|
20259
|
+
} catch (e) {
|
|
20260
|
+
log.error("drafts", `could not write ${file}`, e);
|
|
19805
20261
|
}
|
|
19806
20262
|
}
|
|
19807
20263
|
|
|
@@ -20030,17 +20486,17 @@ class ExecService {
|
|
|
20030
20486
|
}
|
|
20031
20487
|
|
|
20032
20488
|
// ../daemon/src/files/open.ts
|
|
20033
|
-
import { stat as
|
|
20489
|
+
import { stat as stat3 } from "fs/promises";
|
|
20034
20490
|
import { basename as basename3, resolve as resolve7 } from "path";
|
|
20035
20491
|
|
|
20036
20492
|
// ../daemon/src/repos/browse.ts
|
|
20037
20493
|
import { existsSync as existsSync16, statSync as statSync5 } from "fs";
|
|
20038
20494
|
import { readdir } from "fs/promises";
|
|
20039
20495
|
import { homedir as homedir7 } from "os";
|
|
20040
|
-
import { dirname as dirname5, join as
|
|
20496
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
20041
20497
|
var MAX_ENTRIES = 40;
|
|
20042
20498
|
function expandTilde(raw) {
|
|
20043
|
-
return raw === "~" || raw.startsWith("~/") ?
|
|
20499
|
+
return raw === "~" || raw.startsWith("~/") ? join13(homedir7(), raw.slice(1)) : raw;
|
|
20044
20500
|
}
|
|
20045
20501
|
function collapseTilde(abs) {
|
|
20046
20502
|
const home = homedir7();
|
|
@@ -20051,7 +20507,7 @@ function holdsNothing(names) {
|
|
|
20051
20507
|
}
|
|
20052
20508
|
async function describeFolder(abs) {
|
|
20053
20509
|
const path = collapseTilde(abs);
|
|
20054
|
-
if (existsSync16(
|
|
20510
|
+
if (existsSync16(join13(abs, ".git")))
|
|
20055
20511
|
return { path, kind: "project" };
|
|
20056
20512
|
try {
|
|
20057
20513
|
return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
|
|
@@ -20068,7 +20524,7 @@ function describe2(typed, parent) {
|
|
|
20068
20524
|
return {
|
|
20069
20525
|
exists: existsSync16(typed),
|
|
20070
20526
|
isDir,
|
|
20071
|
-
isRepo: isDir && existsSync16(
|
|
20527
|
+
isRepo: isDir && existsSync16(join13(typed, ".git")),
|
|
20072
20528
|
parentExists: existsSync16(parent)
|
|
20073
20529
|
};
|
|
20074
20530
|
}
|
|
@@ -20093,8 +20549,8 @@ async function browsePath(raw) {
|
|
|
20093
20549
|
continue;
|
|
20094
20550
|
if (!name.toLowerCase().startsWith(prefix))
|
|
20095
20551
|
continue;
|
|
20096
|
-
const abs =
|
|
20097
|
-
entries.push({ path: collapseTilde(abs), name, isRepo: existsSync16(
|
|
20552
|
+
const abs = join13(dir, name);
|
|
20553
|
+
entries.push({ path: collapseTilde(abs), name, isRepo: existsSync16(join13(abs, ".git")) });
|
|
20098
20554
|
}
|
|
20099
20555
|
entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
|
|
20100
20556
|
return { entries: entries.slice(0, MAX_ENTRIES), target };
|
|
@@ -20131,7 +20587,7 @@ function looksBinary(text) {
|
|
|
20131
20587
|
|
|
20132
20588
|
// ../daemon/src/files/service.ts
|
|
20133
20589
|
import { spawn as spawn4 } from "child_process";
|
|
20134
|
-
import { stat, unlink } from "fs/promises";
|
|
20590
|
+
import { stat as stat2, unlink } from "fs/promises";
|
|
20135
20591
|
|
|
20136
20592
|
// ../daemon/src/git/blame.ts
|
|
20137
20593
|
var NONE = { commits: [], lines: [] };
|
|
@@ -20202,10 +20658,13 @@ async function withLock(key, fn) {
|
|
|
20202
20658
|
function withRepoLock(repoPath, fn) {
|
|
20203
20659
|
return withLock(repoPath, fn);
|
|
20204
20660
|
}
|
|
20661
|
+
function withFetchLock(repoPath, fn) {
|
|
20662
|
+
return withLock(`fetch:${repoPath}`, fn);
|
|
20663
|
+
}
|
|
20205
20664
|
var fileLockKey = (absPath) => `file:${absPath}`;
|
|
20206
20665
|
|
|
20207
20666
|
// ../daemon/src/git/status.ts
|
|
20208
|
-
import { join as
|
|
20667
|
+
import { join as join14 } from "path";
|
|
20209
20668
|
async function treeFingerprint(worktreePath) {
|
|
20210
20669
|
const [head, diff, untracked] = await Promise.all([
|
|
20211
20670
|
git(worktreePath, "rev-parse", "HEAD"),
|
|
@@ -20215,7 +20674,7 @@ async function treeFingerprint(worktreePath) {
|
|
|
20215
20674
|
const others = await Promise.all(untracked.out.split(`
|
|
20216
20675
|
`).filter(Boolean).map(async (p) => {
|
|
20217
20676
|
try {
|
|
20218
|
-
const f = Bun.file(
|
|
20677
|
+
const f = Bun.file(join14(worktreePath, p));
|
|
20219
20678
|
const body = f.size > 2000000 ? String(f.size) : Bun.hash(await f.arrayBuffer()).toString(36);
|
|
20220
20679
|
return `${p}:${body}`;
|
|
20221
20680
|
} catch {
|
|
@@ -20248,6 +20707,10 @@ async function fileBefore(worktreePath, defaultBr, file) {
|
|
|
20248
20707
|
const r = await gitRaw(worktreePath, "show", `${ref}:${file}`);
|
|
20249
20708
|
return r.ok ? r.out : "";
|
|
20250
20709
|
}
|
|
20710
|
+
async function fileAt(worktreePath, commit, file) {
|
|
20711
|
+
const r = await gitRaw(worktreePath, "show", `${commit}:${file}`);
|
|
20712
|
+
return r.ok ? r.out : "";
|
|
20713
|
+
}
|
|
20251
20714
|
function parseNumstat(out) {
|
|
20252
20715
|
const counts = new Map;
|
|
20253
20716
|
for (const line of out.split(`
|
|
@@ -20266,7 +20729,7 @@ async function numstat(worktreePath, ...range) {
|
|
|
20266
20729
|
}
|
|
20267
20730
|
async function untrackedLines(worktreePath, file) {
|
|
20268
20731
|
try {
|
|
20269
|
-
const f = Bun.file(
|
|
20732
|
+
const f = Bun.file(join14(worktreePath, file));
|
|
20270
20733
|
if (f.size > 2000000)
|
|
20271
20734
|
return {};
|
|
20272
20735
|
const buf = new Uint8Array(await f.arrayBuffer());
|
|
@@ -20439,7 +20902,7 @@ var COMMON = 40;
|
|
|
20439
20902
|
var NEAR = 3;
|
|
20440
20903
|
var CLEAR_LEAD = 1.5;
|
|
20441
20904
|
var ELEMENT_SOURCES_MAX = 8;
|
|
20442
|
-
var
|
|
20905
|
+
var NAME2 = /^[\w-]+$/;
|
|
20443
20906
|
var TAG = /^[a-z][a-z0-9-]*$/;
|
|
20444
20907
|
var NAME_CHAR = /[\w-]/;
|
|
20445
20908
|
function needlesOf(el) {
|
|
@@ -20452,12 +20915,12 @@ function needlesOf(el) {
|
|
|
20452
20915
|
out.set(text, { text, weight, word });
|
|
20453
20916
|
};
|
|
20454
20917
|
if (el.id)
|
|
20455
|
-
add(el.id, 8,
|
|
20918
|
+
add(el.id, 8, NAME2.test(el.id));
|
|
20456
20919
|
const words = (el.text.split(/[&<>"'`${}]/)[0] ?? "").trim().split(/\s+/).slice(0, 6).join(" ");
|
|
20457
20920
|
if (words.length >= 3 && /\p{L}/u.test(words))
|
|
20458
20921
|
add(words, 6, false);
|
|
20459
20922
|
for (const [, value] of el.attrs)
|
|
20460
|
-
add(value, 5,
|
|
20923
|
+
add(value, 5, NAME2.test(value));
|
|
20461
20924
|
for (const c of el.classes)
|
|
20462
20925
|
add(c, 3, true);
|
|
20463
20926
|
return [...out.values()];
|
|
@@ -20623,7 +21086,7 @@ var NOT_WHERE_WRITTEN = [
|
|
|
20623
21086
|
];
|
|
20624
21087
|
var NO_TEXT = { before: "", after: "" };
|
|
20625
21088
|
async function statFile(target) {
|
|
20626
|
-
const st = await
|
|
21089
|
+
const st = await stat2(target).catch((e) => {
|
|
20627
21090
|
if (e.code === "ENOENT")
|
|
20628
21091
|
return null;
|
|
20629
21092
|
throw e;
|
|
@@ -20684,14 +21147,14 @@ class FileService {
|
|
|
20684
21147
|
throw new UserError("unknown worktree");
|
|
20685
21148
|
return r;
|
|
20686
21149
|
}
|
|
20687
|
-
async read(worktreeId, path, ref) {
|
|
21150
|
+
async read(worktreeId, path, ref, since) {
|
|
20688
21151
|
const r = this.require(worktreeId);
|
|
20689
21152
|
const target = resolveInside(r.path, path);
|
|
20690
21153
|
if (ref) {
|
|
20691
21154
|
const { before, after } = await fileAtCommit(r.path, ref, path);
|
|
20692
21155
|
return { ...sides(before, after), version: null, writable: false };
|
|
20693
21156
|
}
|
|
20694
|
-
const before = await fileBefore(r.path, r.base, path);
|
|
21157
|
+
const before = since ? await fileAt(r.path, since, path) : await fileBefore(r.path, r.base, path);
|
|
20695
21158
|
const st = await statFile(target);
|
|
20696
21159
|
if (st && st.size > FILE_MAX_CHARS) {
|
|
20697
21160
|
return { ...NO_TEXT, version: `${st.size}-${st.mtimeMs}`, writable: false, binary: false, tooLarge: true };
|
|
@@ -20870,7 +21333,7 @@ class OpenService {
|
|
|
20870
21333
|
}
|
|
20871
21334
|
async locate(raw) {
|
|
20872
21335
|
const path = canonical(resolve7(expandTilde(raw)));
|
|
20873
|
-
const st = await
|
|
21336
|
+
const st = await stat3(path).catch((e) => {
|
|
20874
21337
|
if (e.code === "ENOENT")
|
|
20875
21338
|
return null;
|
|
20876
21339
|
throw e;
|
|
@@ -21149,9 +21612,9 @@ class AfterLand {
|
|
|
21149
21612
|
}
|
|
21150
21613
|
|
|
21151
21614
|
// ../daemon/src/repos/registry.ts
|
|
21152
|
-
import { existsSync as existsSync20, mkdirSync as
|
|
21615
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync7, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
21153
21616
|
import { homedir as homedir9 } from "os";
|
|
21154
|
-
import { basename as basename5, dirname as dirname7, join as
|
|
21617
|
+
import { basename as basename5, dirname as dirname7, join as join18 } from "path";
|
|
21155
21618
|
|
|
21156
21619
|
// ../daemon/src/git/base.ts
|
|
21157
21620
|
async function resolveBase(repoPath, defaultBranch, route) {
|
|
@@ -21162,8 +21625,8 @@ async function resolveBase(repoPath, defaultBranch, route) {
|
|
|
21162
21625
|
}
|
|
21163
21626
|
|
|
21164
21627
|
// ../daemon/src/repos/config.ts
|
|
21165
|
-
import { closeSync, existsSync as existsSync17, openSync as openSync2, readdirSync as
|
|
21166
|
-
import { join as
|
|
21628
|
+
import { closeSync, existsSync as existsSync17, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync12, readSync, statSync as statSync6 } from "fs";
|
|
21629
|
+
import { join as join15 } from "path";
|
|
21167
21630
|
|
|
21168
21631
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
21169
21632
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -22527,7 +22990,7 @@ function applyEdits(text, edits) {
|
|
|
22527
22990
|
|
|
22528
22991
|
// ../daemon/src/repos/config.ts
|
|
22529
22992
|
var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
|
|
22530
|
-
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync17(
|
|
22993
|
+
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync17(join15(repoPath, rel)));
|
|
22531
22994
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
22532
22995
|
function parseSettings(text) {
|
|
22533
22996
|
const errors = [];
|
|
@@ -22552,7 +23015,7 @@ function readConfigFile(repoPath) {
|
|
|
22552
23015
|
for (const rel of files) {
|
|
22553
23016
|
let raw;
|
|
22554
23017
|
try {
|
|
22555
|
-
raw = parseSettings(readFileSync12(
|
|
23018
|
+
raw = parseSettings(readFileSync12(join15(repoPath, rel), "utf8"));
|
|
22556
23019
|
} catch (e) {
|
|
22557
23020
|
return { ok: false, reason: `${rel} is not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
22558
23021
|
}
|
|
@@ -22611,7 +23074,7 @@ function configTarget(repoPath, made) {
|
|
|
22611
23074
|
}
|
|
22612
23075
|
function configBody(repoPath, rel, config) {
|
|
22613
23076
|
const place = PLACES.find((p) => p.local === rel);
|
|
22614
|
-
const abs = place &&
|
|
23077
|
+
const abs = place && join15(repoPath, place.shared);
|
|
22615
23078
|
if (!abs || !existsSync17(abs))
|
|
22616
23079
|
return config;
|
|
22617
23080
|
let shared;
|
|
@@ -22629,7 +23092,7 @@ function configText(repoPath, rel, config) {
|
|
|
22629
23092
|
let text;
|
|
22630
23093
|
let was;
|
|
22631
23094
|
try {
|
|
22632
|
-
text = readFileSync12(
|
|
23095
|
+
text = readFileSync12(join15(repoPath, rel), "utf8");
|
|
22633
23096
|
was = parseSettings(text);
|
|
22634
23097
|
} catch {
|
|
22635
23098
|
return fresh;
|
|
@@ -22659,7 +23122,7 @@ function detectConfig(repoPath) {
|
|
|
22659
23122
|
return { config: explicit.config, needsSetup: false };
|
|
22660
23123
|
if (explicit)
|
|
22661
23124
|
log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
|
|
22662
|
-
const pkgPath =
|
|
23125
|
+
const pkgPath = join15(repoPath, "package.json");
|
|
22663
23126
|
if (existsSync17(pkgPath)) {
|
|
22664
23127
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
22665
23128
|
const scripts = pkg.scripts ?? {};
|
|
@@ -22682,7 +23145,7 @@ function detectConfig(repoPath) {
|
|
|
22682
23145
|
};
|
|
22683
23146
|
}
|
|
22684
23147
|
}
|
|
22685
|
-
if (existsSync17(
|
|
23148
|
+
if (existsSync17(join15(repoPath, "start.sh"))) {
|
|
22686
23149
|
return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
|
|
22687
23150
|
}
|
|
22688
23151
|
const assumed = assumeNothingToRun(repoPath);
|
|
@@ -22725,24 +23188,24 @@ function assumeNothingToRun(repoPath) {
|
|
|
22725
23188
|
return marker && !servesPage(repoPath) ? marker : undefined;
|
|
22726
23189
|
}
|
|
22727
23190
|
function markerIn(repoPath) {
|
|
22728
|
-
const named = NO_PAGE_MARKERS.find((f) => existsSync17(
|
|
23191
|
+
const named = NO_PAGE_MARKERS.find((f) => existsSync17(join15(repoPath, f)));
|
|
22729
23192
|
if (named)
|
|
22730
23193
|
return named;
|
|
22731
23194
|
let entries;
|
|
22732
23195
|
try {
|
|
22733
|
-
entries =
|
|
23196
|
+
entries = readdirSync5(repoPath);
|
|
22734
23197
|
} catch {
|
|
22735
23198
|
return;
|
|
22736
23199
|
}
|
|
22737
23200
|
return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
22738
23201
|
}
|
|
22739
23202
|
function servesPage(repoPath) {
|
|
22740
|
-
if (WEB_FILES.some((f) => existsSync17(
|
|
23203
|
+
if (WEB_FILES.some((f) => existsSync17(join15(repoPath, f))))
|
|
22741
23204
|
return true;
|
|
22742
|
-
if (/^web:/m.test(head(
|
|
23205
|
+
if (/^web:/m.test(head(join15(repoPath, "Procfile"))))
|
|
22743
23206
|
return true;
|
|
22744
23207
|
for (const [files, names] of WEB_DEPENDENCIES) {
|
|
22745
|
-
const text = files.map((f) => head(
|
|
23208
|
+
const text = files.map((f) => head(join15(repoPath, f))).join(`
|
|
22746
23209
|
`);
|
|
22747
23210
|
if (text.trim() && names.some((n) => mentions(text, n)))
|
|
22748
23211
|
return true;
|
|
@@ -22752,14 +23215,14 @@ function servesPage(repoPath) {
|
|
|
22752
23215
|
function frontEndBeside(repoPath) {
|
|
22753
23216
|
let dirs;
|
|
22754
23217
|
try {
|
|
22755
|
-
dirs =
|
|
23218
|
+
dirs = readdirSync5(repoPath, { withFileTypes: true });
|
|
22756
23219
|
} catch {
|
|
22757
23220
|
return false;
|
|
22758
23221
|
}
|
|
22759
23222
|
return dirs.some((d) => {
|
|
22760
23223
|
if (!d.isDirectory() || d.name.startsWith(".") || NOT_A_PACKAGE.has(d.name))
|
|
22761
23224
|
return false;
|
|
22762
|
-
const pkg = head(
|
|
23225
|
+
const pkg = head(join15(repoPath, d.name, "package.json"));
|
|
22763
23226
|
if (!pkg)
|
|
22764
23227
|
return false;
|
|
22765
23228
|
try {
|
|
@@ -22815,11 +23278,11 @@ function isLocalDatabase(url) {
|
|
|
22815
23278
|
}
|
|
22816
23279
|
function detectServices(repoPath) {
|
|
22817
23280
|
const out = {};
|
|
22818
|
-
const compose = COMPOSE_FILES.find((f) => existsSync17(
|
|
23281
|
+
const compose = COMPOSE_FILES.find((f) => existsSync17(join15(repoPath, f)));
|
|
22819
23282
|
if (compose)
|
|
22820
23283
|
out.compose = compose;
|
|
22821
23284
|
for (const file of ENV_FILES) {
|
|
22822
|
-
const text = head(
|
|
23285
|
+
const text = head(join15(repoPath, file));
|
|
22823
23286
|
if (!text)
|
|
22824
23287
|
continue;
|
|
22825
23288
|
for (const name of DB_URL_NAMES) {
|
|
@@ -22843,7 +23306,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22843
23306
|
return;
|
|
22844
23307
|
let dir = false;
|
|
22845
23308
|
try {
|
|
22846
|
-
dir = statSync6(
|
|
23309
|
+
dir = statSync6(join15(repoPath, clean)).isDirectory();
|
|
22847
23310
|
} catch {
|
|
22848
23311
|
return;
|
|
22849
23312
|
}
|
|
@@ -22854,25 +23317,25 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22854
23317
|
const t = tokens[i];
|
|
22855
23318
|
if (t === "cd") {
|
|
22856
23319
|
const dir = tokens[i + 1];
|
|
22857
|
-
if (dir && !dir.startsWith("-") && existsSync17(
|
|
23320
|
+
if (dir && !dir.startsWith("-") && existsSync17(join15(repoPath, dir)))
|
|
22858
23321
|
add(dir);
|
|
22859
23322
|
continue;
|
|
22860
23323
|
}
|
|
22861
23324
|
if (t.startsWith("-") || t.startsWith("$"))
|
|
22862
23325
|
continue;
|
|
22863
23326
|
const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
|
|
22864
|
-
if (mod?.[1] && existsSync17(
|
|
23327
|
+
if (mod?.[1] && existsSync17(join15(repoPath, mod[1]))) {
|
|
22865
23328
|
add(mod[1]);
|
|
22866
23329
|
continue;
|
|
22867
23330
|
}
|
|
22868
23331
|
const rel = t.replace(/^\.\//, "");
|
|
22869
23332
|
if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
|
|
22870
23333
|
continue;
|
|
22871
|
-
if (!existsSync17(
|
|
23334
|
+
if (!existsSync17(join15(repoPath, rel)))
|
|
22872
23335
|
continue;
|
|
22873
23336
|
add(rel);
|
|
22874
23337
|
if (depth > 0 && rel.endsWith(".sh")) {
|
|
22875
|
-
for (const p of inferPaths(head(
|
|
23338
|
+
for (const p of inferPaths(head(join15(repoPath, rel)), repoPath, depth - 1))
|
|
22876
23339
|
out.add(p);
|
|
22877
23340
|
}
|
|
22878
23341
|
}
|
|
@@ -22895,9 +23358,9 @@ function procCommand(runner, script, body) {
|
|
|
22895
23358
|
return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
|
|
22896
23359
|
}
|
|
22897
23360
|
function detectPages(repoPath, scripts, runner) {
|
|
22898
|
-
if (!scripts.build || !existsSync17(
|
|
23361
|
+
if (!scripts.build || !existsSync17(join15(repoPath, "functions")))
|
|
22899
23362
|
return null;
|
|
22900
|
-
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) =>
|
|
23363
|
+
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join15(repoPath, f)).find((f) => existsSync17(f));
|
|
22901
23364
|
if (!cfg)
|
|
22902
23365
|
return null;
|
|
22903
23366
|
const out = readFileSync12(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
|
|
@@ -22906,20 +23369,20 @@ function detectPages(repoPath, scripts, runner) {
|
|
|
22906
23369
|
return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
|
|
22907
23370
|
}
|
|
22908
23371
|
function detectRunner(repoPath, packageManager) {
|
|
22909
|
-
if (existsSync17(
|
|
23372
|
+
if (existsSync17(join15(repoPath, "bun.lock")) || existsSync17(join15(repoPath, "bun.lockb")))
|
|
22910
23373
|
return "bun";
|
|
22911
|
-
if (existsSync17(
|
|
23374
|
+
if (existsSync17(join15(repoPath, "pnpm-lock.yaml")))
|
|
22912
23375
|
return "pnpm";
|
|
22913
|
-
if (existsSync17(
|
|
23376
|
+
if (existsSync17(join15(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
|
|
22914
23377
|
return "yarn";
|
|
22915
23378
|
return "npm";
|
|
22916
23379
|
}
|
|
22917
23380
|
|
|
22918
23381
|
// ../daemon/src/repos/create.ts
|
|
22919
|
-
import { existsSync as existsSync18, readdirSync as
|
|
23382
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
22920
23383
|
import { appendFile as appendFile2, mkdir as mkdir2, rm } from "fs/promises";
|
|
22921
23384
|
import { homedir as homedir8 } from "os";
|
|
22922
|
-
import { dirname as dirname6, join as
|
|
23385
|
+
import { dirname as dirname6, join as join16, resolve as resolve8 } from "path";
|
|
22923
23386
|
function isInside(dir, parent) {
|
|
22924
23387
|
const p = parent.replace(/\/+$/, "");
|
|
22925
23388
|
return dir === p || dir.startsWith(`${p}/`);
|
|
@@ -22930,29 +23393,29 @@ function planProject(opts, home = homedir8()) {
|
|
|
22930
23393
|
throw new UserError(nameError);
|
|
22931
23394
|
const first = firstProjectsFolder(opts.parent, home);
|
|
22932
23395
|
const parent = first ?? resolveParent(opts.parent);
|
|
22933
|
-
const dir =
|
|
23396
|
+
const dir = join16(parent, opts.name.trim());
|
|
22934
23397
|
if (existsSync18(dir))
|
|
22935
23398
|
throw new UserError(`${dir} already exists`);
|
|
22936
23399
|
return { parent, dir, makeParent: first !== null };
|
|
22937
23400
|
}
|
|
22938
23401
|
function firstProjectsFolder(raw, home) {
|
|
22939
23402
|
const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
|
|
22940
|
-
if (typed !==
|
|
23403
|
+
if (typed !== join16(home, PROJECTS_FOLDER) || existsSync18(typed) || !existsSync18(home))
|
|
22941
23404
|
return null;
|
|
22942
|
-
return
|
|
23405
|
+
return join16(realpathSync2(home), PROJECTS_FOLDER);
|
|
22943
23406
|
}
|
|
22944
23407
|
function planInPlace(opts) {
|
|
22945
23408
|
const name = opts.name.trim();
|
|
22946
23409
|
if (!name || name === "." || name === ".." || name.includes("/")) {
|
|
22947
23410
|
throw new UserError(`${opts.name} is not a folder name`);
|
|
22948
23411
|
}
|
|
22949
|
-
const typed =
|
|
23412
|
+
const typed = join16(resolveParent(opts.parent), name);
|
|
22950
23413
|
if (!existsSync18(typed) || !statSync7(typed).isDirectory())
|
|
22951
23414
|
throw new UserError(`${typed} is not a folder`);
|
|
22952
23415
|
const dir = realpathSync2(typed);
|
|
22953
|
-
if (existsSync18(
|
|
23416
|
+
if (existsSync18(join16(dir, ".git")))
|
|
22954
23417
|
throw new UserError(`${dir} is already a project; open it instead`);
|
|
22955
|
-
if (!holdsNothing(
|
|
23418
|
+
if (!holdsNothing(readdirSync6(dir))) {
|
|
22956
23419
|
throw new UserError(`${dir} is not empty; only an empty folder can become a project where it is`);
|
|
22957
23420
|
}
|
|
22958
23421
|
return { parent: dirname6(dir), dir, makeParent: false };
|
|
@@ -23041,13 +23504,13 @@ async function undoCreate(dir) {
|
|
|
23041
23504
|
}
|
|
23042
23505
|
async function undoInit(dir) {
|
|
23043
23506
|
try {
|
|
23044
|
-
await rm(
|
|
23507
|
+
await rm(join16(dir, ".git"), { recursive: true, force: true });
|
|
23045
23508
|
} catch (e) {
|
|
23046
23509
|
log.warn(dir, "could not take back a half-made .git", e);
|
|
23047
23510
|
}
|
|
23048
23511
|
}
|
|
23049
23512
|
async function unmakeProject(dir, made) {
|
|
23050
|
-
const target = made === "folder" ? dir :
|
|
23513
|
+
const target = made === "folder" ? dir : join16(dir, ".git");
|
|
23051
23514
|
try {
|
|
23052
23515
|
await rm(target, { recursive: true, force: true });
|
|
23053
23516
|
} catch (e) {
|
|
@@ -23078,7 +23541,7 @@ async function ok(p, what) {
|
|
|
23078
23541
|
|
|
23079
23542
|
// ../daemon/src/repos/watcher.ts
|
|
23080
23543
|
import { existsSync as existsSync19, watch } from "fs";
|
|
23081
|
-
import { basename as basename4, join as
|
|
23544
|
+
import { basename as basename4, join as join17 } from "path";
|
|
23082
23545
|
function watchDefaultBranch(repoPath, branch, onMove) {
|
|
23083
23546
|
const last = { local: null, upstream: null };
|
|
23084
23547
|
const read = async () => {
|
|
@@ -23131,13 +23594,13 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
23131
23594
|
]);
|
|
23132
23595
|
if (stopped)
|
|
23133
23596
|
return;
|
|
23134
|
-
const gitDir = common.ok && common.out ? common.out :
|
|
23597
|
+
const gitDir = common.ok && common.out ? common.out : join17(repoPath, ".git");
|
|
23135
23598
|
tryWatch(gitDir);
|
|
23136
|
-
tryWatch(
|
|
23599
|
+
tryWatch(join17(gitDir, "refs", "heads"));
|
|
23137
23600
|
if (!remote.ok || !remote.out)
|
|
23138
23601
|
return;
|
|
23139
|
-
const remotes =
|
|
23140
|
-
const mine =
|
|
23602
|
+
const remotes = join17(gitDir, "refs", "remotes");
|
|
23603
|
+
const mine = join17(remotes, remote.out);
|
|
23141
23604
|
let inner = null;
|
|
23142
23605
|
let mid = null;
|
|
23143
23606
|
const armInner = () => {
|
|
@@ -23153,7 +23616,7 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
23153
23616
|
}) : null;
|
|
23154
23617
|
armInner();
|
|
23155
23618
|
};
|
|
23156
|
-
tryWatch(
|
|
23619
|
+
tryWatch(join17(gitDir, "refs"), (_event, filename) => {
|
|
23157
23620
|
if (!filename || filename === "remotes")
|
|
23158
23621
|
armMid();
|
|
23159
23622
|
schedule();
|
|
@@ -23182,7 +23645,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
23182
23645
|
}, 300);
|
|
23183
23646
|
};
|
|
23184
23647
|
const arm = (commonDir) => {
|
|
23185
|
-
const dir =
|
|
23648
|
+
const dir = join17(commonDir, "worktrees");
|
|
23186
23649
|
const armInner = () => {
|
|
23187
23650
|
inner?.close();
|
|
23188
23651
|
inner = null;
|
|
@@ -23225,7 +23688,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
23225
23688
|
function watchConfigFile(repoPath, onChange) {
|
|
23226
23689
|
const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
|
|
23227
23690
|
const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename4(rel)));
|
|
23228
|
-
const dir =
|
|
23691
|
+
const dir = join17(repoPath, CONFIG_DIR);
|
|
23229
23692
|
let timer = null;
|
|
23230
23693
|
let outer = null;
|
|
23231
23694
|
let inner = null;
|
|
@@ -23362,7 +23825,7 @@ class RepoRegistry {
|
|
|
23362
23825
|
return `${repo.name} is set up now`;
|
|
23363
23826
|
if (main.promptedAt || main.lastTurn)
|
|
23364
23827
|
return `${repo.name} has a chat now`;
|
|
23365
|
-
if (existsSync20(
|
|
23828
|
+
if (existsSync20(join18(repo.path, ".claude")))
|
|
23366
23829
|
return `an agent has already run in ${repo.name}`;
|
|
23367
23830
|
if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
|
|
23368
23831
|
return `${repo.name} has files in it now`;
|
|
@@ -23513,10 +23976,10 @@ class RepoRegistry {
|
|
|
23513
23976
|
throw new UserError(current.reason);
|
|
23514
23977
|
const was = configTarget(repo.path, !!repo.made);
|
|
23515
23978
|
const rel = configSibling(was, kind);
|
|
23516
|
-
const moved = rel !== was && existsSync20(
|
|
23979
|
+
const moved = rel !== was && existsSync20(join18(repo.path, was));
|
|
23517
23980
|
if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
|
|
23518
23981
|
try {
|
|
23519
|
-
|
|
23982
|
+
rmSync3(join18(repo.path, was));
|
|
23520
23983
|
} catch (e) {
|
|
23521
23984
|
log.warn(repoId, `could not remove ${was}`, e);
|
|
23522
23985
|
}
|
|
@@ -23525,9 +23988,9 @@ class RepoRegistry {
|
|
|
23525
23988
|
}
|
|
23526
23989
|
if (isLocalConfigFile(rel))
|
|
23527
23990
|
await excludeFromGit(repo.path, rel);
|
|
23528
|
-
const file =
|
|
23991
|
+
const file = join18(repo.path, rel);
|
|
23529
23992
|
try {
|
|
23530
|
-
|
|
23993
|
+
mkdirSync7(dirname7(file), { recursive: true });
|
|
23531
23994
|
writeFileSync7(file, configText(repo.path, rel, config));
|
|
23532
23995
|
} catch (e) {
|
|
23533
23996
|
log.warn(repoId, `could not write ${rel}`, e);
|
|
@@ -23629,7 +24092,7 @@ class RepoRegistry {
|
|
|
23629
24092
|
}
|
|
23630
24093
|
placeConfig(repo) {
|
|
23631
24094
|
const rel = configTarget(repo.path, !!repo.made);
|
|
23632
|
-
if (isLocalConfigFile(rel) && existsSync20(
|
|
24095
|
+
if (isLocalConfigFile(rel) && existsSync20(join18(repo.path, rel))) {
|
|
23633
24096
|
fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
|
|
23634
24097
|
}
|
|
23635
24098
|
if (repo.configFile === rel)
|
|
@@ -23721,7 +24184,7 @@ class RepoRegistry {
|
|
|
23721
24184
|
}
|
|
23722
24185
|
|
|
23723
24186
|
// ../daemon/src/routes/service.ts
|
|
23724
|
-
import { join as
|
|
24187
|
+
import { join as join19 } from "path";
|
|
23725
24188
|
|
|
23726
24189
|
// ../daemon/src/routes/fileRouters.ts
|
|
23727
24190
|
var SKIP = /(^|\/)(node_modules|dist|build|out|coverage|\.next|\.nuxt|\.output|\.svelte-kit|\.vercel|\.netlify)\//;
|
|
@@ -24921,7 +25384,7 @@ class RouteService {
|
|
|
24921
25384
|
return mergeRoutes(fileRoutes(paths, roots), code);
|
|
24922
25385
|
}
|
|
24923
25386
|
async cachedRead(root, path, had, kept, decode) {
|
|
24924
|
-
const abs =
|
|
25387
|
+
const abs = join19(root, path);
|
|
24925
25388
|
const stat = await this.fs.stat(abs);
|
|
24926
25389
|
if (!stat)
|
|
24927
25390
|
return null;
|
|
@@ -24946,7 +25409,7 @@ class RouteService {
|
|
|
24946
25409
|
}
|
|
24947
25410
|
}
|
|
24948
25411
|
async hash(root, file) {
|
|
24949
|
-
const abs =
|
|
25412
|
+
const abs = join19(root, file);
|
|
24950
25413
|
const stat = await this.fs.stat(abs);
|
|
24951
25414
|
if (!stat)
|
|
24952
25415
|
return null;
|
|
@@ -25268,7 +25731,7 @@ function servedLocally(origin) {
|
|
|
25268
25731
|
import { homedir as homedir11 } from "os";
|
|
25269
25732
|
|
|
25270
25733
|
// ../daemon/src/core/pair.ts
|
|
25271
|
-
import { randomBytes as
|
|
25734
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
25272
25735
|
class PairCodes {
|
|
25273
25736
|
now;
|
|
25274
25737
|
live = null;
|
|
@@ -25276,7 +25739,7 @@ class PairCodes {
|
|
|
25276
25739
|
this.now = now;
|
|
25277
25740
|
}
|
|
25278
25741
|
mint() {
|
|
25279
|
-
const code =
|
|
25742
|
+
const code = randomBytes4(9).toString("base64url");
|
|
25280
25743
|
this.live = { code, until: this.now() + PAIR_TTL_MS };
|
|
25281
25744
|
return { code, ms: PAIR_TTL_MS };
|
|
25282
25745
|
}
|
|
@@ -25287,10 +25750,13 @@ class PairCodes {
|
|
|
25287
25750
|
}
|
|
25288
25751
|
}
|
|
25289
25752
|
|
|
25753
|
+
// ../daemon/src/server/handlers.ts
|
|
25754
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
25755
|
+
|
|
25290
25756
|
// ../daemon/src/agent/config.ts
|
|
25291
25757
|
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
|
|
25292
25758
|
import { homedir as homedir10 } from "os";
|
|
25293
|
-
import { join as
|
|
25759
|
+
import { join as join20 } from "path";
|
|
25294
25760
|
function agentConfigFiles(spec, repoPath, home = homedir10()) {
|
|
25295
25761
|
const file = (id, label, path) => ({
|
|
25296
25762
|
id,
|
|
@@ -25300,30 +25766,30 @@ function agentConfigFiles(spec, repoPath, home = homedir10()) {
|
|
|
25300
25766
|
});
|
|
25301
25767
|
if (spec.id === "claude") {
|
|
25302
25768
|
return [
|
|
25303
|
-
file("user-config", "user config (MCP servers, projects)",
|
|
25304
|
-
file("user-settings", "user settings (permissions, hooks)",
|
|
25305
|
-
...repoPath ? [file("project-mcp", "project MCP servers",
|
|
25306
|
-
...repoPath ? [file("project-settings", "project settings",
|
|
25769
|
+
file("user-config", "user config (MCP servers, projects)", join20(home, ".claude.json")),
|
|
25770
|
+
file("user-settings", "user settings (permissions, hooks)", join20(home, ".claude", "settings.json")),
|
|
25771
|
+
...repoPath ? [file("project-mcp", "project MCP servers", join20(repoPath, ".mcp.json"))] : [],
|
|
25772
|
+
...repoPath ? [file("project-settings", "project settings", join20(repoPath, ".claude", "settings.json"))] : []
|
|
25307
25773
|
];
|
|
25308
25774
|
}
|
|
25309
25775
|
if (spec.id === "codex")
|
|
25310
|
-
return [file("user-config", "config",
|
|
25776
|
+
return [file("user-config", "config", join20(home, ".codex", "config.toml"))];
|
|
25311
25777
|
return [];
|
|
25312
25778
|
}
|
|
25313
25779
|
function describeAgentConfig(spec, repoPath, home = homedir10()) {
|
|
25314
25780
|
const files = agentConfigFiles(spec, repoPath, home);
|
|
25315
25781
|
const servers = [];
|
|
25316
25782
|
if (spec.id === "claude") {
|
|
25317
|
-
const user = readJson(
|
|
25783
|
+
const user = readJson(join20(home, ".claude.json"));
|
|
25318
25784
|
servers.push(...serversOf(user?.mcpServers, "user"));
|
|
25319
25785
|
if (repoPath) {
|
|
25320
25786
|
const projects = user?.projects;
|
|
25321
25787
|
const mine = projects?.[repoPath];
|
|
25322
25788
|
servers.push(...serversOf(mine?.mcpServers, "local"));
|
|
25323
|
-
servers.push(...serversOf(readJson(
|
|
25789
|
+
servers.push(...serversOf(readJson(join20(repoPath, ".mcp.json"))?.mcpServers, "project"));
|
|
25324
25790
|
}
|
|
25325
25791
|
} else if (spec.id === "codex") {
|
|
25326
|
-
servers.push(...codexServers(
|
|
25792
|
+
servers.push(...codexServers(join20(home, ".codex", "config.toml")));
|
|
25327
25793
|
}
|
|
25328
25794
|
return { agent: spec.id, files, servers };
|
|
25329
25795
|
}
|
|
@@ -25961,6 +26427,9 @@ var handlers = {
|
|
|
25961
26427
|
s.drafts.set(msg.boxId, msg.text, msg.clientId);
|
|
25962
26428
|
s.worktrees.spare.typed(msg.boxId, msg.text);
|
|
25963
26429
|
},
|
|
26430
|
+
"set-attachments"(msg, _ctx, s) {
|
|
26431
|
+
s.drafts.setAttachments(msg.boxId, msg.items, msg.clientId);
|
|
26432
|
+
},
|
|
25964
26433
|
"refresh-git"(msg, _ctx, s) {
|
|
25965
26434
|
s.worktrees.recount(msg.repoId);
|
|
25966
26435
|
},
|
|
@@ -25973,19 +26442,17 @@ var handlers = {
|
|
|
25973
26442
|
"forget-visit"(msg, _ctx, s) {
|
|
25974
26443
|
s.routes.forget(msg.repoId, msg.path);
|
|
25975
26444
|
},
|
|
25976
|
-
async chat(msg,
|
|
25977
|
-
await s.worktrees.send(msg.worktreeId, {
|
|
26445
|
+
async chat(msg, ctx, s) {
|
|
26446
|
+
await sending(s, ctx, msg, msg.attachments, () => s.worktrees.send(msg.worktreeId, {
|
|
25978
26447
|
text: msg.text,
|
|
25979
26448
|
clientId: msg.clientId,
|
|
25980
26449
|
context: msg.context,
|
|
25981
26450
|
attachments: msg.attachments
|
|
25982
|
-
});
|
|
26451
|
+
}));
|
|
25983
26452
|
},
|
|
25984
|
-
async "create-worktree"(msg,
|
|
25985
|
-
|
|
26453
|
+
async "create-worktree"(msg, ctx, s) {
|
|
26454
|
+
const opts = {
|
|
25986
26455
|
createdBy: msg.clientId,
|
|
25987
|
-
worktreeId: msg.worktreeId,
|
|
25988
|
-
variant: msg.variant,
|
|
25989
26456
|
context: msg.context,
|
|
25990
26457
|
attachments: msg.attachments,
|
|
25991
26458
|
agent: msg.agent,
|
|
@@ -25994,6 +26461,30 @@ var handlers = {
|
|
|
25994
26461
|
model: msg.model,
|
|
25995
26462
|
effort: msg.effort,
|
|
25996
26463
|
carry: msg.carry
|
|
26464
|
+
};
|
|
26465
|
+
const of = msg.variants ?? 1;
|
|
26466
|
+
await sending(s, ctx, msg, msg.attachments, async () => {
|
|
26467
|
+
if (of === 1) {
|
|
26468
|
+
await s.worktrees.create(msg.repoId, msg.prompt, { ...opts, worktreeId: msg.worktreeId });
|
|
26469
|
+
return;
|
|
26470
|
+
}
|
|
26471
|
+
const group = randomBytes5(4).toString("hex");
|
|
26472
|
+
const made = await Promise.allSettled(Array.from({ length: of }, (_, i) => s.worktrees.create(msg.repoId, msg.prompt, {
|
|
26473
|
+
...opts,
|
|
26474
|
+
...i === 0 ? { worktreeId: msg.worktreeId } : {},
|
|
26475
|
+
variant: { group, index: i + 1, of }
|
|
26476
|
+
})));
|
|
26477
|
+
const failed = made.flatMap((m) => m.status === "rejected" ? [m.reason] : []);
|
|
26478
|
+
if (failed.length === 0)
|
|
26479
|
+
return;
|
|
26480
|
+
const went = failed.find((e) => e instanceof UserError && e.delivered);
|
|
26481
|
+
if (went)
|
|
26482
|
+
throw went;
|
|
26483
|
+
if (failed.length < of) {
|
|
26484
|
+
const why = failed[0] instanceof Error ? failed[0].message : String(failed[0]);
|
|
26485
|
+
throw new UserError(`${failed.length} of ${of} attempts did not start: ${why}`, { delivered: true });
|
|
26486
|
+
}
|
|
26487
|
+
throw failed[0];
|
|
25997
26488
|
});
|
|
25998
26489
|
},
|
|
25999
26490
|
"set-worktree-profile"(msg, _ctx, s) {
|
|
@@ -26016,6 +26507,8 @@ var handlers = {
|
|
|
26016
26507
|
},
|
|
26017
26508
|
"batch-worktrees"(msg, ctx, s) {
|
|
26018
26509
|
const repo = s.state.requireRepo(msg.repoId);
|
|
26510
|
+
if (msg.boxId)
|
|
26511
|
+
s.drafts.take(msg.boxId);
|
|
26019
26512
|
fireAndForget(msg.repoId, (async () => {
|
|
26020
26513
|
const agent = msg.agent ?? s.state.defaultAgent ?? DEFAULT_AGENT_ID;
|
|
26021
26514
|
const tasks = await s.planTasks(msg.prompt, repo.path, agent) ?? [msg.prompt];
|
|
@@ -26039,8 +26532,10 @@ var handlers = {
|
|
|
26039
26532
|
"list-archived"(msg, ctx, s) {
|
|
26040
26533
|
ctx.reply({ t: "archived", repoId: msg.repoId, items: s.worktrees.archived(msg.repoId) });
|
|
26041
26534
|
},
|
|
26042
|
-
async "restore-worktree"(msg,
|
|
26043
|
-
await s
|
|
26535
|
+
async "restore-worktree"(msg, ctx, s) {
|
|
26536
|
+
await sending(s, ctx, msg, msg.message?.attachments, async () => {
|
|
26537
|
+
await s.worktrees.restore(msg.archiveId, msg.clientId, msg.message);
|
|
26538
|
+
});
|
|
26044
26539
|
},
|
|
26045
26540
|
async "delete-archived"(msg, _ctx, s) {
|
|
26046
26541
|
await s.worktrees.deleteArchived(msg.archiveId);
|
|
@@ -26052,10 +26547,17 @@ var handlers = {
|
|
|
26052
26547
|
await gitStatus(s, ctx, msg.worktreeId);
|
|
26053
26548
|
},
|
|
26054
26549
|
async "read-file"(msg, ctx, s) {
|
|
26055
|
-
const head = {
|
|
26550
|
+
const head = {
|
|
26551
|
+
t: "file-read",
|
|
26552
|
+
worktreeId: msg.worktreeId,
|
|
26553
|
+
path: msg.path,
|
|
26554
|
+
ref: msg.ref,
|
|
26555
|
+
since: msg.since,
|
|
26556
|
+
seq: msg.seq
|
|
26557
|
+
};
|
|
26056
26558
|
try {
|
|
26057
26559
|
const kept = s.worktrees.readable(msg.worktreeId) ? null : await s.worktrees.archivedFile(msg.worktreeId, msg.path, msg.ref);
|
|
26058
|
-
const read = kept ? keptRead(kept.before, kept.after) : await s.files.read(msg.worktreeId, msg.path, msg.ref);
|
|
26560
|
+
const read = kept ? keptRead(kept.before, kept.after) : await s.files.read(msg.worktreeId, msg.path, msg.ref, msg.since);
|
|
26059
26561
|
ctx.reply({ ...head, ...read });
|
|
26060
26562
|
} catch (e) {
|
|
26061
26563
|
if (!(e instanceof UserError))
|
|
@@ -26123,8 +26625,8 @@ var handlers = {
|
|
|
26123
26625
|
"update-now"(_msg, _ctx, s) {
|
|
26124
26626
|
return s.update.updateNow();
|
|
26125
26627
|
},
|
|
26126
|
-
"check-update"(_msg,
|
|
26127
|
-
|
|
26628
|
+
async "check-update"(_msg, ctx, s) {
|
|
26629
|
+
ctx.reply({ t: "update-checked", check: await s.update.checkNow() });
|
|
26128
26630
|
},
|
|
26129
26631
|
async commit(msg, ctx, s) {
|
|
26130
26632
|
const result = await s.worktrees.commit(msg.worktreeId, msg.message);
|
|
@@ -26168,7 +26670,21 @@ var handlers = {
|
|
|
26168
26670
|
},
|
|
26169
26671
|
unqueue(msg, _ctx, s) {
|
|
26170
26672
|
requireRun(s, msg.worktreeId);
|
|
26171
|
-
s.runtime.agentFor(msg.worktreeId)?.unqueue(msg.index);
|
|
26673
|
+
s.runtime.agentFor(msg.worktreeId)?.unqueue(msg.index, msg.edit ? (m) => s.drafts.putBack(msg.worktreeId, { text: m.text, items: m.attachments ?? [] }) : undefined);
|
|
26674
|
+
},
|
|
26675
|
+
async redraft(msg, _ctx, s) {
|
|
26676
|
+
const r = s.worktrees.readable(msg.worktreeId);
|
|
26677
|
+
const events = r?.wt ? s.runtime.ensureAgent(r.wt).agent.transcript() : s.worktrees.archivedTranscript(msg.worktreeId) ?? [];
|
|
26678
|
+
const event = events.find((e) => e.seq === msg.seq)?.event;
|
|
26679
|
+
if (event?.type !== "user-message")
|
|
26680
|
+
throw new UserError("that message is no longer in this chat");
|
|
26681
|
+
const items = [];
|
|
26682
|
+
for (const ref of event.attachments ?? []) {
|
|
26683
|
+
const item = await s.attachments.reattach(msg.worktreeId, ref, (file) => s.worktrees.archivedAttachment(msg.worktreeId, file));
|
|
26684
|
+
if (item)
|
|
26685
|
+
items.push(item);
|
|
26686
|
+
}
|
|
26687
|
+
s.drafts.putBack(msg.worktreeId, { text: event.text, items });
|
|
26172
26688
|
},
|
|
26173
26689
|
async "changed-ranges"(msg, ctx, s) {
|
|
26174
26690
|
const { ranges, lineOffset } = await s.files.changedRanges(msg.worktreeId, msg.path);
|
|
@@ -26368,6 +26884,10 @@ var handlers = {
|
|
|
26368
26884
|
if (!s.runs.stop(msg.worktreeId, msg.kind))
|
|
26369
26885
|
throw new UserError(`no ${msg.kind} is running here`);
|
|
26370
26886
|
},
|
|
26887
|
+
"preview-start"(msg, _ctx, s) {
|
|
26888
|
+
requireRun(s, msg.worktreeId);
|
|
26889
|
+
s.worktrees.startPreview(msg.worktreeId);
|
|
26890
|
+
},
|
|
26371
26891
|
async "search-refs"(msg, ctx, s) {
|
|
26372
26892
|
const refs = await s.refs.search(msg.repoId, msg.query);
|
|
26373
26893
|
ctx.reply({ t: "refs", repoId: msg.repoId, query: msg.query, refs });
|
|
@@ -26380,6 +26900,30 @@ var handlers = {
|
|
|
26380
26900
|
await s.worktrees.openRef(msg.repoId, msg.kind, msg.ref, { createdBy: msg.clientId, pr: msg.pr });
|
|
26381
26901
|
}
|
|
26382
26902
|
};
|
|
26903
|
+
async function sending(s, ctx, from, attachments, deliver) {
|
|
26904
|
+
const uploads = s.attachments.uploads;
|
|
26905
|
+
const { boxId, clientId } = from;
|
|
26906
|
+
const named = uploadIds(attachments);
|
|
26907
|
+
const boxed = boxId ? uploadIds(s.drafts.attachments(boxId)) : [];
|
|
26908
|
+
const ids = [...new Set([...named, ...boxed])].filter((id) => uploads.has(id));
|
|
26909
|
+
uploads.hold(ids);
|
|
26910
|
+
const was = boxId ? s.drafts.take(boxId, clientId) : null;
|
|
26911
|
+
try {
|
|
26912
|
+
if (named.some((id) => !uploads.has(id)))
|
|
26913
|
+
throw new UserError(GONE);
|
|
26914
|
+
await deliver();
|
|
26915
|
+
} catch (e) {
|
|
26916
|
+
if (!boxId || !was || e instanceof UserError && e.delivered)
|
|
26917
|
+
throw e;
|
|
26918
|
+
if (!(e instanceof UserError))
|
|
26919
|
+
log.error("ws", "a message reached no agent", e);
|
|
26920
|
+
const items = was.items.filter((a) => !("upload" in a) || uploads.has(a.upload));
|
|
26921
|
+
s.drafts.putBack(boxId, { text: was.text, items }, clientId);
|
|
26922
|
+
ctx.reply({ t: "unsent", boxId, text: was.text, items, message: e instanceof Error ? e.message : String(e) });
|
|
26923
|
+
} finally {
|
|
26924
|
+
uploads.release(ids);
|
|
26925
|
+
}
|
|
26926
|
+
}
|
|
26383
26927
|
function looseCwd(s, id, stream) {
|
|
26384
26928
|
const disc = s.worktrees.discoveredById(id);
|
|
26385
26929
|
if (!disc)
|
|
@@ -26405,9 +26949,16 @@ async function dispatch(msg, ctx, s) {
|
|
|
26405
26949
|
|
|
26406
26950
|
// ../daemon/src/server/http.ts
|
|
26407
26951
|
import { existsSync as existsSync23 } from "fs";
|
|
26408
|
-
import { join as
|
|
26952
|
+
import { join as join21 } from "path";
|
|
26409
26953
|
var IMMUTABLE = "private, max-age=31536000, immutable";
|
|
26410
26954
|
var NO_STORE = "no-store";
|
|
26955
|
+
function attachedHeaders(imageType) {
|
|
26956
|
+
return {
|
|
26957
|
+
"content-type": imageType ?? "text/plain; charset=utf-8",
|
|
26958
|
+
"x-content-type-options": "nosniff",
|
|
26959
|
+
...imageType ? {} : { "content-security-policy": "sandbox" }
|
|
26960
|
+
};
|
|
26961
|
+
}
|
|
26411
26962
|
function createFetch(opts) {
|
|
26412
26963
|
const grant = previewGrant(opts.token);
|
|
26413
26964
|
const remote = opts.remote;
|
|
@@ -26541,6 +27092,37 @@ function createFetch(opts) {
|
|
|
26541
27092
|
return new Response("open failed; see daemon log", { status: 500 });
|
|
26542
27093
|
}
|
|
26543
27094
|
}
|
|
27095
|
+
if (url.pathname === "/uploads" && req.method === "POST") {
|
|
27096
|
+
if (!sameSecret(req.headers.get("authorization"), `Bearer ${opts.token}`)) {
|
|
27097
|
+
return new Response("unauthorized", { status: 401 });
|
|
27098
|
+
}
|
|
27099
|
+
const kind = url.searchParams.get("kind");
|
|
27100
|
+
if (!isUploadKind(kind))
|
|
27101
|
+
return new Response("missing kind", { status: 400 });
|
|
27102
|
+
const mime = (req.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
27103
|
+
try {
|
|
27104
|
+
const body = req.body;
|
|
27105
|
+
return Response.json(await opts.attachments.uploads.put(kind, mime, body), {
|
|
27106
|
+
headers: { "cache-control": NO_STORE }
|
|
27107
|
+
});
|
|
27108
|
+
} catch (e) {
|
|
27109
|
+
if (e instanceof UserError)
|
|
27110
|
+
return new Response(e.message, { status: 400 });
|
|
27111
|
+
log.error("http", "upload failed", e);
|
|
27112
|
+
return new Response("upload failed; see daemon log", { status: 500 });
|
|
27113
|
+
}
|
|
27114
|
+
}
|
|
27115
|
+
if (url.pathname.startsWith("/uploads/")) {
|
|
27116
|
+
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
27117
|
+
return new Response("unauthorized", { status: 401 });
|
|
27118
|
+
const id = url.pathname.slice("/uploads/".length);
|
|
27119
|
+
const path = opts.attachments.uploads.path(id);
|
|
27120
|
+
if (!path || !existsSync23(path))
|
|
27121
|
+
return new Response("not found", { status: 404 });
|
|
27122
|
+
return new Response(Bun.file(path), {
|
|
27123
|
+
headers: { "cache-control": IMMUTABLE, ...attachedHeaders(opts.attachments.uploads.imageType(id)) }
|
|
27124
|
+
});
|
|
27125
|
+
}
|
|
26544
27126
|
if (url.pathname.startsWith("/attachments/")) {
|
|
26545
27127
|
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
26546
27128
|
return new Response("unauthorized", { status: 401 });
|
|
@@ -26551,7 +27133,9 @@ function createFetch(opts) {
|
|
|
26551
27133
|
const path = places.find((p) => !!p && existsSync23(p));
|
|
26552
27134
|
if (!path)
|
|
26553
27135
|
return new Response("not found", { status: 404 });
|
|
26554
|
-
return new Response(Bun.file(path), {
|
|
27136
|
+
return new Response(Bun.file(path), {
|
|
27137
|
+
headers: { "cache-control": IMMUTABLE, ...attachedHeaders(drawnType(file ?? "")) }
|
|
27138
|
+
});
|
|
26555
27139
|
}
|
|
26556
27140
|
if (url.pathname.startsWith("/files/")) {
|
|
26557
27141
|
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
@@ -26579,14 +27163,14 @@ function createFetch(opts) {
|
|
|
26579
27163
|
});
|
|
26580
27164
|
}
|
|
26581
27165
|
const rel = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
26582
|
-
const file =
|
|
27166
|
+
const file = join21(opts.shellDist, rel.replaceAll("..", ""));
|
|
26583
27167
|
const hashed = rel.startsWith("/assets/");
|
|
26584
27168
|
if (existsSync23(file) && Bun.file(file).size > 0) {
|
|
26585
27169
|
return new Response(Bun.file(file), { headers: { "cache-control": hashed ? IMMUTABLE : NO_STORE } });
|
|
26586
27170
|
}
|
|
26587
27171
|
if (hashed)
|
|
26588
27172
|
return new Response("not found", { status: 404 });
|
|
26589
|
-
const index =
|
|
27173
|
+
const index = join21(opts.shellDist, "index.html");
|
|
26590
27174
|
if (existsSync23(index))
|
|
26591
27175
|
return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE } });
|
|
26592
27176
|
return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
|
|
@@ -26598,6 +27182,22 @@ var DROP_ABOVE_BYTES = 4 * 1024 * 1024;
|
|
|
26598
27182
|
var DROPPED_NOTICE = `\r
|
|
26599
27183
|
[toyon] output dropped: this pane was too far behind\r
|
|
26600
27184
|
`;
|
|
27185
|
+
var SENDS = new Set(["chat", "create-worktree", "restore-worktree", "batch-worktrees"]);
|
|
27186
|
+
function sentFrom(json) {
|
|
27187
|
+
if (!json || typeof json !== "object")
|
|
27188
|
+
return null;
|
|
27189
|
+
const { t, boxId } = json;
|
|
27190
|
+
if (typeof t !== "string" || !SENDS.has(t))
|
|
27191
|
+
return null;
|
|
27192
|
+
return typeof boxId === "string" && boxId.length > 0 && boxId.length <= 200 ? boxId : null;
|
|
27193
|
+
}
|
|
27194
|
+
function unparsedReply(json, reason, drafts) {
|
|
27195
|
+
const message = `invalid message: ${reason}`;
|
|
27196
|
+
const boxId = sentFrom(json);
|
|
27197
|
+
if (boxId === null)
|
|
27198
|
+
return { t: "error", message };
|
|
27199
|
+
return { t: "unsent", boxId, text: drafts.text(boxId), items: drafts.attachments(boxId), message };
|
|
27200
|
+
}
|
|
26601
27201
|
function startServer(opts) {
|
|
26602
27202
|
const { services: s, token, version } = opts;
|
|
26603
27203
|
const sockets = new Set;
|
|
@@ -26695,6 +27295,7 @@ function startServer(opts) {
|
|
|
26695
27295
|
},
|
|
26696
27296
|
costs: s.idle.costs(),
|
|
26697
27297
|
updates: s.update.status(),
|
|
27298
|
+
agents: s.agents.adapters(),
|
|
26698
27299
|
sockets: [...sockets].map((ws) => ({ subs: [...ws.data.subs], sent: ws.data.sent, bytes: ws.data.bytes }))
|
|
26699
27300
|
});
|
|
26700
27301
|
let statusesInFlight = false;
|
|
@@ -26822,6 +27423,7 @@ function startServer(opts) {
|
|
|
26822
27423
|
s.hub.on("visitsChanged", (repoId) => broadcast({ t: "visits", repoId, pages: s.routes.history(repoId) }));
|
|
26823
27424
|
s.hub.on("archiveChanged", (repoId) => broadcast({ t: "archived", repoId, items: s.worktrees.archived(repoId) }));
|
|
26824
27425
|
s.hub.on("draftChanged", (boxId, text, clientId) => broadcast({ t: "draft", boxId, text, ...clientId ? { clientId } : {} }));
|
|
27426
|
+
s.hub.on("attachmentsChanged", (boxId, items, clientId) => broadcast({ t: "attachments", boxId, items, ...clientId ? { clientId } : {} }));
|
|
26825
27427
|
const helloFrame = async () => {
|
|
26826
27428
|
await s.update.refresh();
|
|
26827
27429
|
return {
|
|
@@ -26848,11 +27450,13 @@ function startServer(opts) {
|
|
|
26848
27450
|
self: s.self.get(),
|
|
26849
27451
|
update: s.update.get(),
|
|
26850
27452
|
drafts: s.drafts.all(),
|
|
27453
|
+
attachments: s.drafts.allAttachments(),
|
|
26851
27454
|
managed: managedView(opts.managed)
|
|
26852
27455
|
};
|
|
26853
27456
|
};
|
|
26854
27457
|
let branded = false;
|
|
26855
27458
|
const serverConfig = {
|
|
27459
|
+
maxRequestBodySize: UPLOAD_MAX_BYTES,
|
|
26856
27460
|
hostname: cloud.bindHost,
|
|
26857
27461
|
fetch: createFetch({
|
|
26858
27462
|
token,
|
|
@@ -26880,7 +27484,6 @@ function startServer(opts) {
|
|
|
26880
27484
|
}
|
|
26881
27485
|
}),
|
|
26882
27486
|
websocket: {
|
|
26883
|
-
maxPayloadLength: 64 * 1024 * 1024,
|
|
26884
27487
|
async open(ws) {
|
|
26885
27488
|
const preview = ws.data.preview;
|
|
26886
27489
|
if (preview) {
|
|
@@ -26922,7 +27525,7 @@ function startServer(opts) {
|
|
|
26922
27525
|
}
|
|
26923
27526
|
const parsed = parseClientMsg(json);
|
|
26924
27527
|
if (!parsed.ok) {
|
|
26925
|
-
send(ws,
|
|
27528
|
+
send(ws, unparsedReply(json, parsed.reason, s.drafts));
|
|
26926
27529
|
return;
|
|
26927
27530
|
}
|
|
26928
27531
|
const ctx = {
|
|
@@ -26989,16 +27592,16 @@ function startServer(opts) {
|
|
|
26989
27592
|
}
|
|
26990
27593
|
|
|
26991
27594
|
// ../daemon/src/themes/store.ts
|
|
26992
|
-
import { existsSync as existsSync24, readdirSync as
|
|
27595
|
+
import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync15, statSync as statSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
26993
27596
|
import { homedir as homedir12 } from "os";
|
|
26994
|
-
import { basename as basename6, dirname as dirname8, join as
|
|
27597
|
+
import { basename as basename6, dirname as dirname8, join as join22, resolve as resolve9 } from "path";
|
|
26995
27598
|
var home = homedir12();
|
|
26996
27599
|
var defaultExtensionDirs = [
|
|
26997
|
-
|
|
26998
|
-
|
|
26999
|
-
|
|
27000
|
-
|
|
27001
|
-
|
|
27600
|
+
join22(home, ".vscode", "extensions"),
|
|
27601
|
+
join22(home, ".vscode-insiders", "extensions"),
|
|
27602
|
+
join22(home, ".vscode-oss", "extensions"),
|
|
27603
|
+
join22(home, ".cursor", "extensions"),
|
|
27604
|
+
join22(home, ".windsurf", "extensions"),
|
|
27002
27605
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/extensions",
|
|
27003
27606
|
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions",
|
|
27004
27607
|
"/Applications/Cursor.app/Contents/Resources/app/extensions",
|
|
@@ -27082,7 +27685,7 @@ class ThemeStore {
|
|
|
27082
27685
|
name: json?.name ?? base,
|
|
27083
27686
|
source: "file"
|
|
27084
27687
|
});
|
|
27085
|
-
writeFileSync8(
|
|
27688
|
+
writeFileSync8(join22(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
|
|
27086
27689
|
`);
|
|
27087
27690
|
this.load();
|
|
27088
27691
|
return theme;
|
|
@@ -27114,10 +27717,10 @@ function loadDir(dir) {
|
|
|
27114
27717
|
if (!existsSync24(dir))
|
|
27115
27718
|
return [];
|
|
27116
27719
|
const out = [];
|
|
27117
|
-
for (const f of
|
|
27720
|
+
for (const f of readdirSync7(dir)) {
|
|
27118
27721
|
if (!/\.jsonc?$/i.test(f))
|
|
27119
27722
|
continue;
|
|
27120
|
-
const file =
|
|
27723
|
+
const file = join22(dir, f);
|
|
27121
27724
|
const id = `file:${slug(f.replace(/\.(json|jsonc)$/i, ""))}`;
|
|
27122
27725
|
try {
|
|
27123
27726
|
const json = readJsonc(file);
|
|
@@ -27138,13 +27741,13 @@ function discover(dir) {
|
|
|
27138
27741
|
const out = [];
|
|
27139
27742
|
let entries;
|
|
27140
27743
|
try {
|
|
27141
|
-
entries =
|
|
27744
|
+
entries = readdirSync7(dir);
|
|
27142
27745
|
} catch {
|
|
27143
27746
|
return out;
|
|
27144
27747
|
}
|
|
27145
27748
|
for (const name of entries) {
|
|
27146
|
-
const extDir =
|
|
27147
|
-
const pkgFile =
|
|
27749
|
+
const extDir = join22(dir, name);
|
|
27750
|
+
const pkgFile = join22(extDir, "package.json");
|
|
27148
27751
|
try {
|
|
27149
27752
|
if (!statSync9(extDir).isDirectory() || !existsSync24(pkgFile))
|
|
27150
27753
|
continue;
|
|
@@ -27154,7 +27757,7 @@ function discover(dir) {
|
|
|
27154
27757
|
continue;
|
|
27155
27758
|
const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
|
|
27156
27759
|
let nls = {};
|
|
27157
|
-
const nlsFile =
|
|
27760
|
+
const nlsFile = join22(extDir, "package.nls.json");
|
|
27158
27761
|
if (existsSync24(nlsFile)) {
|
|
27159
27762
|
try {
|
|
27160
27763
|
nls = JSON.parse(readFileSync15(nlsFile, "utf8"));
|
|
@@ -27295,16 +27898,20 @@ class UpdateService {
|
|
|
27295
27898
|
async check() {
|
|
27296
27899
|
if (this.d.method === "none" || this.d.managedBy)
|
|
27297
27900
|
return;
|
|
27901
|
+
await this.ask();
|
|
27902
|
+
}
|
|
27903
|
+
async ask() {
|
|
27298
27904
|
const answer = await this.d.latest();
|
|
27299
27905
|
this.registryName = answer.registry;
|
|
27300
27906
|
if (answer.version === null) {
|
|
27301
27907
|
this.unreachable = answer.registry;
|
|
27302
|
-
return;
|
|
27908
|
+
return { registry: answer.registry, latest: null };
|
|
27303
27909
|
}
|
|
27304
27910
|
this.unreachable = null;
|
|
27305
27911
|
this.latest = newer(answer.version, this.d.running) ? answer.version : null;
|
|
27306
27912
|
this.announce();
|
|
27307
27913
|
await this.auto();
|
|
27914
|
+
return { registry: answer.registry, latest: answer.version };
|
|
27308
27915
|
}
|
|
27309
27916
|
async updateNow() {
|
|
27310
27917
|
this.refuseManaged();
|
|
@@ -27321,14 +27928,17 @@ class UpdateService {
|
|
|
27321
27928
|
async checkNow() {
|
|
27322
27929
|
this.refuseManaged();
|
|
27323
27930
|
if (this.d.method === "none") {
|
|
27324
|
-
|
|
27931
|
+
const answer = await this.d.latest();
|
|
27932
|
+
return { registry: answer.registry, latest: answer.version };
|
|
27325
27933
|
}
|
|
27326
|
-
await this.
|
|
27327
|
-
|
|
27328
|
-
|
|
27329
|
-
|
|
27330
|
-
|
|
27934
|
+
const answer = await this.ask();
|
|
27935
|
+
const target = this.target();
|
|
27936
|
+
if (target !== null && !this.wanted && !(this.needsInstall() && this.d.command(target) === null)) {
|
|
27937
|
+
this.failed = null;
|
|
27938
|
+
this.wanted = true;
|
|
27939
|
+
fireAndForget("update", this.proceed(), "update");
|
|
27331
27940
|
}
|
|
27941
|
+
return answer;
|
|
27332
27942
|
}
|
|
27333
27943
|
install() {
|
|
27334
27944
|
return this.d.method;
|
|
@@ -27563,8 +28173,8 @@ class BackendShare {
|
|
|
27563
28173
|
// ../daemon/src/worktrees/cache.ts
|
|
27564
28174
|
import { createHash as createHash3 } from "crypto";
|
|
27565
28175
|
import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
|
|
27566
|
-
import { mkdir as mkdir4, readdir as readdir2, rename, rm as rm3, stat as
|
|
27567
|
-
import { join as
|
|
28176
|
+
import { mkdir as mkdir4, readdir as readdir2, rename, rm as rm3, stat as stat4, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
28177
|
+
import { join as join23 } from "path";
|
|
27568
28178
|
|
|
27569
28179
|
// ../daemon/src/core/clone.ts
|
|
27570
28180
|
import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
@@ -27595,7 +28205,7 @@ var STALE_MS = 30 * 24 * 3600000;
|
|
|
27595
28205
|
var TMP = ".tmp-";
|
|
27596
28206
|
var MANIFEST = "manifest.json";
|
|
27597
28207
|
function defaultTools(dir) {
|
|
27598
|
-
const has = (...files) => files.some((f) => existsSync25(
|
|
28208
|
+
const has = (...files) => files.some((f) => existsSync25(join23(dir, f)));
|
|
27599
28209
|
const out = [];
|
|
27600
28210
|
if (has("bun.lock", "bun.lockb"))
|
|
27601
28211
|
out.push("bun --version");
|
|
@@ -27608,7 +28218,7 @@ function defaultTools(dir) {
|
|
|
27608
28218
|
return out;
|
|
27609
28219
|
}
|
|
27610
28220
|
function withSidecars(root, rel) {
|
|
27611
|
-
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(
|
|
28221
|
+
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(join23(root, n)));
|
|
27612
28222
|
}
|
|
27613
28223
|
var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
|
|
27614
28224
|
async function listEntries(familyDir) {
|
|
@@ -27623,8 +28233,8 @@ async function listEntries(familyDir) {
|
|
|
27623
28233
|
const m = /^([0-9a-f]{12})-([0-9a-z]+)$/.exec(name);
|
|
27624
28234
|
if (!m?.[1] || !m[2])
|
|
27625
28235
|
continue;
|
|
27626
|
-
const dir =
|
|
27627
|
-
if (!existsSync25(
|
|
28236
|
+
const dir = join23(familyDir, name);
|
|
28237
|
+
if (!existsSync25(join23(dir, MANIFEST)))
|
|
27628
28238
|
continue;
|
|
27629
28239
|
out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
|
|
27630
28240
|
}
|
|
@@ -27654,7 +28264,7 @@ class ArtifactCache {
|
|
|
27654
28264
|
try {
|
|
27655
28265
|
const family = await this.family(wt, repo, policy);
|
|
27656
28266
|
const base = await this.baseCommit(wt, repo);
|
|
27657
|
-
const entries = await listEntries(
|
|
28267
|
+
const entries = await listEntries(join23(this.d.paths.cacheDir, repo.id, family.key));
|
|
27658
28268
|
const entry = entries.find((e) => e.base === base.slice(0, 12)) ?? entries[0];
|
|
27659
28269
|
if (!entry) {
|
|
27660
28270
|
say(`cache: nothing kept for these lockfiles and tools on ${family.platform}`);
|
|
@@ -27662,14 +28272,14 @@ class ArtifactCache {
|
|
|
27662
28272
|
}
|
|
27663
28273
|
const restored = [];
|
|
27664
28274
|
for (const rel of policy.paths) {
|
|
27665
|
-
if (existsSync25(
|
|
28275
|
+
if (existsSync25(join23(wt.path, rel)))
|
|
27666
28276
|
continue;
|
|
27667
28277
|
const names = withSidecars(entry.dir, rel);
|
|
27668
28278
|
if (names.length === 0)
|
|
27669
28279
|
continue;
|
|
27670
28280
|
let whole = true;
|
|
27671
28281
|
for (const name of names) {
|
|
27672
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
28282
|
+
const r = await (this.d.clone ?? cloneTree)(join23(entry.dir, name), join23(wt.path, name));
|
|
27673
28283
|
if (r.ok)
|
|
27674
28284
|
continue;
|
|
27675
28285
|
say(`cache: could not restore ${name}: ${r.error}`);
|
|
@@ -27703,12 +28313,12 @@ class ArtifactCache {
|
|
|
27703
28313
|
let tmp;
|
|
27704
28314
|
let slot;
|
|
27705
28315
|
try {
|
|
27706
|
-
const present = policy.paths.filter((rel) => existsSync25(
|
|
28316
|
+
const present = policy.paths.filter((rel) => existsSync25(join23(wt.path, rel)));
|
|
27707
28317
|
if (present.length === 0)
|
|
27708
28318
|
return;
|
|
27709
28319
|
const family = await this.family(wt, repo, policy);
|
|
27710
28320
|
const base = await this.baseCommit(wt, repo);
|
|
27711
|
-
const familyDir =
|
|
28321
|
+
const familyDir = join23(this.d.paths.cacheDir, repo.id, family.key);
|
|
27712
28322
|
const key = `${familyDir}/${base.slice(0, 12)}`;
|
|
27713
28323
|
const kept = (await listEntries(familyDir)).find((e) => e.base === base.slice(0, 12));
|
|
27714
28324
|
if (kept || this.making.has(key)) {
|
|
@@ -27719,21 +28329,21 @@ class ArtifactCache {
|
|
|
27719
28329
|
this.making.add(key);
|
|
27720
28330
|
const at = Date.now();
|
|
27721
28331
|
const name = entryName(base, at);
|
|
27722
|
-
tmp =
|
|
28332
|
+
tmp = join23(familyDir, `${TMP}${name}`);
|
|
27723
28333
|
await mkdir4(tmp, { recursive: true });
|
|
27724
28334
|
let how = "clonefile";
|
|
27725
28335
|
for (const rel of present) {
|
|
27726
28336
|
for (const n of withSidecars(wt.path, rel)) {
|
|
27727
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
28337
|
+
const r = await (this.d.clone ?? cloneTree)(join23(wt.path, n), join23(tmp, n));
|
|
27728
28338
|
if (!r.ok)
|
|
27729
28339
|
throw new Error(`could not keep ${n}: ${r.error}`);
|
|
27730
28340
|
how = r.how;
|
|
27731
28341
|
}
|
|
27732
28342
|
}
|
|
27733
28343
|
const manifest = { at, base, platform: family.platform, tools: family.tools, paths: present };
|
|
27734
|
-
await writeFile2(
|
|
28344
|
+
await writeFile2(join23(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
|
|
27735
28345
|
`);
|
|
27736
|
-
await rename(tmp,
|
|
28346
|
+
await rename(tmp, join23(familyDir, name));
|
|
27737
28347
|
tmp = undefined;
|
|
27738
28348
|
log.info(worktreeId, `cache: kept ${list(present)} for ${base.slice(0, 7)} via ${how} in ${Date.now() - started}ms`);
|
|
27739
28349
|
await this.prune(familyDir);
|
|
@@ -27761,7 +28371,7 @@ class ArtifactCache {
|
|
|
27761
28371
|
return;
|
|
27762
28372
|
}
|
|
27763
28373
|
for (const repoId of repoIds) {
|
|
27764
|
-
const repoDir =
|
|
28374
|
+
const repoDir = join23(root, repoId);
|
|
27765
28375
|
if (!this.d.state.repo(repoId)) {
|
|
27766
28376
|
await rm3(repoDir, { recursive: true, force: true });
|
|
27767
28377
|
continue;
|
|
@@ -27773,7 +28383,7 @@ class ArtifactCache {
|
|
|
27773
28383
|
continue;
|
|
27774
28384
|
}
|
|
27775
28385
|
for (const family of families) {
|
|
27776
|
-
const familyDir =
|
|
28386
|
+
const familyDir = join23(repoDir, family);
|
|
27777
28387
|
let names;
|
|
27778
28388
|
try {
|
|
27779
28389
|
names = await readdir2(familyDir);
|
|
@@ -27782,11 +28392,11 @@ class ArtifactCache {
|
|
|
27782
28392
|
}
|
|
27783
28393
|
for (const n of names)
|
|
27784
28394
|
if (n.startsWith(TMP))
|
|
27785
|
-
await rm3(
|
|
28395
|
+
await rm3(join23(familyDir, n), { recursive: true, force: true });
|
|
27786
28396
|
const entries = await listEntries(familyDir);
|
|
27787
28397
|
let newest = 0;
|
|
27788
28398
|
for (const e of entries) {
|
|
27789
|
-
const s = await
|
|
28399
|
+
const s = await stat4(e.dir).catch(() => null);
|
|
27790
28400
|
if (s)
|
|
27791
28401
|
newest = Math.max(newest, s.mtimeMs);
|
|
27792
28402
|
}
|
|
@@ -27802,7 +28412,7 @@ class ArtifactCache {
|
|
|
27802
28412
|
for (const f of policy.key) {
|
|
27803
28413
|
h.update(`\x00${f}\x00`);
|
|
27804
28414
|
try {
|
|
27805
|
-
h.update(readFileSync16(
|
|
28415
|
+
h.update(readFileSync16(join23(wt.path, f)));
|
|
27806
28416
|
} catch {
|
|
27807
28417
|
h.update("(absent)");
|
|
27808
28418
|
}
|
|
@@ -27983,9 +28593,14 @@ class LandingService {
|
|
|
27983
28593
|
d.hub.on("agentStatus", (id, status) => {
|
|
27984
28594
|
this.note(id, status);
|
|
27985
28595
|
if (status === "working")
|
|
27986
|
-
this.
|
|
28596
|
+
this.turnStarts(id);
|
|
27987
28597
|
});
|
|
27988
28598
|
}
|
|
28599
|
+
turnStarts(worktreeId) {
|
|
28600
|
+
if (!this.d.state.worktree(worktreeId)?.landing?.ready)
|
|
28601
|
+
return this.clear(worktreeId);
|
|
28602
|
+
this.judging.delete(worktreeId);
|
|
28603
|
+
}
|
|
27989
28604
|
note(worktreeId, status) {
|
|
27990
28605
|
if (status === "working" || status === "waiting")
|
|
27991
28606
|
this.busy.add(worktreeId);
|
|
@@ -28011,14 +28626,69 @@ class LandingService {
|
|
|
28011
28626
|
const wt = this.d.state.worktree(worktreeId);
|
|
28012
28627
|
if (!wt || !canLand(wt))
|
|
28013
28628
|
return;
|
|
28014
|
-
|
|
28015
|
-
|
|
28629
|
+
const held = await this.standing(wt);
|
|
28630
|
+
if (turn.end !== "done") {
|
|
28631
|
+
if (!held)
|
|
28632
|
+
this.clear(worktreeId);
|
|
28633
|
+
return;
|
|
28634
|
+
}
|
|
28016
28635
|
if (!await this.hasWork(wt)) {
|
|
28017
28636
|
this.clear(worktreeId);
|
|
28018
28637
|
return this.recapAnswer(wt, turn);
|
|
28019
28638
|
}
|
|
28639
|
+
if (held)
|
|
28640
|
+
return this.reword(wt, turn, held);
|
|
28020
28641
|
await this.run(wt, { at: turn.at, ask: true, quiet: false });
|
|
28021
28642
|
}
|
|
28643
|
+
async standing(wt) {
|
|
28644
|
+
const l = wt.landing;
|
|
28645
|
+
if (!l?.ready)
|
|
28646
|
+
return;
|
|
28647
|
+
return l.fingerprint === await treeFingerprint(wt.path) ? l : undefined;
|
|
28648
|
+
}
|
|
28649
|
+
async reword(wt, turn, held) {
|
|
28650
|
+
if (!this.d.judge)
|
|
28651
|
+
return;
|
|
28652
|
+
const worktreeId = wt.id;
|
|
28653
|
+
const repo = this.d.state.requireRepo(wt.repoId);
|
|
28654
|
+
this.judging.set(worktreeId, turn.at);
|
|
28655
|
+
const started = Date.now();
|
|
28656
|
+
const entries = this.d.transcript(worktreeId);
|
|
28657
|
+
const prompt = landPrompt({
|
|
28658
|
+
title: wt.title,
|
|
28659
|
+
firstAsk: firstAskOf(entries),
|
|
28660
|
+
turns: turnsSince(entries, 0),
|
|
28661
|
+
diffStat: await diffSummary(wt.path, baseOf(repo)),
|
|
28662
|
+
recentSubjects: await recentSubjects(wt.path, baseOf(repo))
|
|
28663
|
+
});
|
|
28664
|
+
let verdict = null;
|
|
28665
|
+
try {
|
|
28666
|
+
verdict = await this.d.judge(wt, prompt);
|
|
28667
|
+
} catch (e) {
|
|
28668
|
+
log.warn(worktreeId, "landing verdict kept its words: the question went unanswered", e);
|
|
28669
|
+
}
|
|
28670
|
+
if (this.stopping || this.judging.get(worktreeId) !== turn.at)
|
|
28671
|
+
return;
|
|
28672
|
+
this.judging.delete(worktreeId);
|
|
28673
|
+
const row = this.d.state.worktree(worktreeId);
|
|
28674
|
+
const now = row?.landing;
|
|
28675
|
+
if (!verdict || !row || !now?.ready || now.fingerprint !== held.fingerprint)
|
|
28676
|
+
return;
|
|
28677
|
+
const { why: _was, stale: _moved, ...rest } = now;
|
|
28678
|
+
const caveat = await this.sharedMigration(wt, repo);
|
|
28679
|
+
const doubt = [!verdict.ready && verdict.why ? verdict.why : "", caveat ?? ""].filter(Boolean).join(". ");
|
|
28680
|
+
const landing = { ...rest, at: turn.at, ...doubt ? { why: doubt } : {} };
|
|
28681
|
+
if (!landing.subject && verdict.subject) {
|
|
28682
|
+
landing.subject = verdict.subject;
|
|
28683
|
+
if (verdict.body)
|
|
28684
|
+
landing.body = verdict.body;
|
|
28685
|
+
delete landing.unanswered;
|
|
28686
|
+
}
|
|
28687
|
+
if (row.lastTurn?.at === turn.at && verdict.recap)
|
|
28688
|
+
row.lastTurn.recap = { at: Date.now(), text: verdict.recap };
|
|
28689
|
+
log.info(worktreeId, `landing: tree unchanged, check not run again${doubt ? ", doubted" : ""}, ${Date.now() - started}ms`);
|
|
28690
|
+
this.d.worktrees.setLanding(worktreeId, landing);
|
|
28691
|
+
}
|
|
28022
28692
|
async recapAnswer(wt, turn) {
|
|
28023
28693
|
if (!this.d.recap)
|
|
28024
28694
|
return;
|
|
@@ -28572,12 +29242,12 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
|
|
|
28572
29242
|
}
|
|
28573
29243
|
|
|
28574
29244
|
// ../daemon/src/worktrees/service.ts
|
|
28575
|
-
import { cpSync, existsSync as
|
|
29245
|
+
import { cpSync, existsSync as existsSync29, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
|
|
28576
29246
|
import { rm as rm4 } from "fs/promises";
|
|
28577
|
-
import { dirname as dirname11, join as
|
|
29247
|
+
import { dirname as dirname11, join as join27 } from "path";
|
|
28578
29248
|
|
|
28579
29249
|
// ../daemon/src/git/archive.ts
|
|
28580
|
-
import { rmSync as
|
|
29250
|
+
import { rmSync as rmSync4 } from "fs";
|
|
28581
29251
|
var archiveRef = (worktreeId) => `refs/toyon/archive/${worktreeId}`;
|
|
28582
29252
|
var SNAPSHOT_SUBJECT = "toyon: uncommitted work";
|
|
28583
29253
|
var SNAPSHOT_AUTHOR = "toyon";
|
|
@@ -28620,7 +29290,7 @@ async function snapshotCommit(wtPath, head, indexFile) {
|
|
|
28620
29290
|
], wtPath);
|
|
28621
29291
|
return commit.ok ? commit.out : null;
|
|
28622
29292
|
} finally {
|
|
28623
|
-
|
|
29293
|
+
rmSync4(indexFile, { force: true });
|
|
28624
29294
|
}
|
|
28625
29295
|
}
|
|
28626
29296
|
async function keptAt(repoPath, ref) {
|
|
@@ -28742,9 +29412,12 @@ function overwritten(text) {
|
|
|
28742
29412
|
}
|
|
28743
29413
|
return files;
|
|
28744
29414
|
}
|
|
29415
|
+
var listed = (files) => files.length > 3 ? `${files.slice(0, 3).join(", ")} and ${files.length - 3} more` : files.join(", ");
|
|
28745
29416
|
function inTheWay(defaultBr, what, files) {
|
|
28746
|
-
|
|
28747
|
-
|
|
29417
|
+
return `${defaultBr} has uncommitted files the ${what} would overwrite (${listed(files)}): commit or stash them there first`;
|
|
29418
|
+
}
|
|
29419
|
+
function untrackedInTheWay(defaultBr, files) {
|
|
29420
|
+
return `${defaultBr} has files that are untracked here (${listed(files)}): move or delete them, then sync`;
|
|
28748
29421
|
}
|
|
28749
29422
|
async function requireMainReady(repoPath, defaultBr, clean) {
|
|
28750
29423
|
const current = await git(repoPath, "branch", "--show-current");
|
|
@@ -28756,18 +29429,75 @@ async function requireMainReady(repoPath, defaultBr, clean) {
|
|
|
28756
29429
|
}
|
|
28757
29430
|
return null;
|
|
28758
29431
|
}
|
|
28759
|
-
|
|
28760
|
-
|
|
28761
|
-
|
|
28762
|
-
|
|
29432
|
+
var CARRIED = "refs/worktree/toyon/carried";
|
|
29433
|
+
async function setAside(worktreePath) {
|
|
29434
|
+
const head = await git(worktreePath, "rev-parse", "HEAD");
|
|
29435
|
+
const made = await git(worktreePath, "stash", "create");
|
|
29436
|
+
if (!head.ok || !made.ok) {
|
|
29437
|
+
return { ok: false, message: `could not set the uncommitted changes aside: ${made.err.slice(0, 200)}` };
|
|
29438
|
+
}
|
|
29439
|
+
if (!made.out)
|
|
29440
|
+
return null;
|
|
29441
|
+
const kept = await git(worktreePath, "update-ref", CARRIED, made.out);
|
|
29442
|
+
const cleared = kept.ok ? await git(worktreePath, "reset", "--hard", "-q") : kept;
|
|
29443
|
+
if (!cleared.ok) {
|
|
29444
|
+
return { ok: false, message: `could not set the uncommitted changes aside: ${cleared.err.slice(0, 200)}` };
|
|
29445
|
+
}
|
|
29446
|
+
return { sha: made.out, head: head.out };
|
|
29447
|
+
}
|
|
29448
|
+
async function putBack(worktreePath, defaultBr, aside, taken) {
|
|
29449
|
+
let clash = null;
|
|
29450
|
+
if (taken.ok) {
|
|
29451
|
+
const applied = await git(worktreePath, "stash", "apply", aside.sha);
|
|
29452
|
+
if (applied.ok) {
|
|
29453
|
+
await git(worktreePath, "update-ref", "-d", CARRIED);
|
|
29454
|
+
return taken;
|
|
29455
|
+
}
|
|
29456
|
+
const unmerged = await git(worktreePath, "diff", "--name-only", "--diff-filter=U");
|
|
29457
|
+
clash = unmerged.out.split(`
|
|
29458
|
+
`).filter(Boolean);
|
|
29459
|
+
await git(worktreePath, "reset", "--hard", "-q", aside.head);
|
|
29460
|
+
}
|
|
29461
|
+
const back = await git(worktreePath, "stash", "apply", "--index", aside.sha);
|
|
29462
|
+
if (!back.ok) {
|
|
29463
|
+
return {
|
|
29464
|
+
ok: false,
|
|
29465
|
+
message: `sync stopped with the uncommitted changes set aside: git stash apply ${CARRIED} in the worktree brings them back`
|
|
29466
|
+
};
|
|
29467
|
+
}
|
|
29468
|
+
await git(worktreePath, "update-ref", "-d", CARRIED);
|
|
29469
|
+
if (!clash)
|
|
29470
|
+
return taken;
|
|
29471
|
+
const named = clash.length ? ` (${listed(clash)})` : "";
|
|
29472
|
+
return {
|
|
29473
|
+
ok: false,
|
|
29474
|
+
message: `the uncommitted changes here conflict with ${defaultBr}${named}: commit them first, then sync`
|
|
29475
|
+
};
|
|
29476
|
+
}
|
|
29477
|
+
async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = false) {
|
|
29478
|
+
if (!carry) {
|
|
29479
|
+
const cErr = await requireClean(worktreePath);
|
|
29480
|
+
if (cErr)
|
|
29481
|
+
return cErr;
|
|
29482
|
+
}
|
|
28763
29483
|
const { behind } = await aheadBehind(worktreePath, defaultBr);
|
|
28764
29484
|
if (behind === 0)
|
|
28765
29485
|
return { ok: true, message: `already up to date with ${defaultBr}` };
|
|
29486
|
+
const aside = carry ? await setAside(worktreePath) : null;
|
|
29487
|
+
if (aside && "message" in aside)
|
|
29488
|
+
return aside;
|
|
29489
|
+
const taken = await bringIn(worktreePath, defaultBr, own, behind, w);
|
|
29490
|
+
return aside ? putBack(worktreePath, defaultBr, aside, taken) : taken;
|
|
29491
|
+
}
|
|
29492
|
+
async function bringIn(worktreePath, defaultBr, own, behind, w) {
|
|
28766
29493
|
if (own) {
|
|
28767
29494
|
w.step(`rebasing onto ${defaultBr}`);
|
|
28768
29495
|
const r = await w.git(worktreePath, ["rebase", defaultBr]);
|
|
28769
29496
|
if (!r.ok) {
|
|
28770
29497
|
await git(worktreePath, "rebase", "--abort");
|
|
29498
|
+
const files = overwritten(r.text);
|
|
29499
|
+
if (files.length)
|
|
29500
|
+
return { ok: false, message: untrackedInTheWay(defaultBr, files) };
|
|
28771
29501
|
const ask = `rebase onto ${defaultBr} conflicts: ask the agent to bring ${defaultBr} in and resolve them`;
|
|
28772
29502
|
return {
|
|
28773
29503
|
ok: false,
|
|
@@ -28780,6 +29510,9 @@ async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED) {
|
|
|
28780
29510
|
w.step(`merging ${defaultBr} in`);
|
|
28781
29511
|
const m = await w.git(worktreePath, ["merge", "--no-edit", defaultBr]);
|
|
28782
29512
|
if (!m.ok) {
|
|
29513
|
+
const files = overwritten(m.text);
|
|
29514
|
+
if (files.length)
|
|
29515
|
+
return { ok: false, message: untrackedInTheWay(defaultBr, files) };
|
|
28783
29516
|
return mergeFailure(worktreePath, m, `sync conflicts with ${defaultBr}: ask the agent to merge ${defaultBr} and resolve them`);
|
|
28784
29517
|
}
|
|
28785
29518
|
return { ok: true, message: `synced ${behind} commit(s) from ${defaultBr}` };
|
|
@@ -29032,18 +29765,142 @@ function compareUrl(remoteUrl, base, branch) {
|
|
|
29032
29765
|
return `https://github.com/${m[1]}/compare/${encodeURIComponent(base)}...${encodeURIComponent(branch)}?expand=1`;
|
|
29033
29766
|
}
|
|
29034
29767
|
|
|
29768
|
+
// ../daemon/src/git/turn.ts
|
|
29769
|
+
import { copyFileSync, existsSync as existsSync26, rmSync as rmSync5 } from "fs";
|
|
29770
|
+
import { join as join24 } from "path";
|
|
29771
|
+
var turnRef = (worktreeId, which) => `refs/toyon/turns/${worktreeId}/${which}`;
|
|
29772
|
+
var SNAPSHOT_SUBJECT2 = "toyon: tree at a send";
|
|
29773
|
+
var queue = new Map;
|
|
29774
|
+
function inTurn(wtPath, job) {
|
|
29775
|
+
const next = (queue.get(wtPath) ?? Promise.resolve()).then(job, job);
|
|
29776
|
+
queue.set(wtPath, next.catch(() => {}));
|
|
29777
|
+
return next;
|
|
29778
|
+
}
|
|
29779
|
+
var synced = new Map;
|
|
29780
|
+
var MAX_PATHS = 400;
|
|
29781
|
+
async function treeNow(wtPath, head, dirty) {
|
|
29782
|
+
let was = synced.get(wtPath);
|
|
29783
|
+
let index = was?.index;
|
|
29784
|
+
if (!index) {
|
|
29785
|
+
const dir = await git(wtPath, "rev-parse", "--absolute-git-dir");
|
|
29786
|
+
if (!dir.ok || !dir.out)
|
|
29787
|
+
return null;
|
|
29788
|
+
index = join24(dir.out, "toyon-turn-index");
|
|
29789
|
+
}
|
|
29790
|
+
const env = { GIT_INDEX_FILE: index };
|
|
29791
|
+
const writeTree = async () => {
|
|
29792
|
+
const tree = await run(GIT, ["write-tree"], wtPath, env);
|
|
29793
|
+
return tree.ok && tree.out ? tree.out : null;
|
|
29794
|
+
};
|
|
29795
|
+
if (!existsSync26(index))
|
|
29796
|
+
was = undefined;
|
|
29797
|
+
const paths = dirty && !dirty.some((f) => /[RC]/.test(f.xy)) ? dirty.map((f) => f.path) : null;
|
|
29798
|
+
if (was && paths && was.head === head) {
|
|
29799
|
+
const again = [...new Set([...was.dirty, ...paths])];
|
|
29800
|
+
if (again.length <= MAX_PATHS) {
|
|
29801
|
+
const added = again.length === 0 || (await run(GIT, ["--literal-pathspecs", "add", "-A", "--", ...again], wtPath, env)).ok;
|
|
29802
|
+
const tree = added ? await writeTree() : null;
|
|
29803
|
+
if (tree) {
|
|
29804
|
+
synced.set(wtPath, { index, head, dirty: new Set(paths) });
|
|
29805
|
+
return tree;
|
|
29806
|
+
}
|
|
29807
|
+
}
|
|
29808
|
+
}
|
|
29809
|
+
synced.delete(wtPath);
|
|
29810
|
+
if (!existsSync26(index)) {
|
|
29811
|
+
const own = join24(index, "..", "index");
|
|
29812
|
+
if (existsSync26(own))
|
|
29813
|
+
copyFileSync(own, index);
|
|
29814
|
+
else if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
|
|
29815
|
+
return null;
|
|
29816
|
+
}
|
|
29817
|
+
const full = async () => (await run(GIT, ["add", "-A"], wtPath, env)).ok ? writeTree() : null;
|
|
29818
|
+
let tree = await full();
|
|
29819
|
+
if (!tree) {
|
|
29820
|
+
rmSync5(index, { force: true });
|
|
29821
|
+
if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
|
|
29822
|
+
return null;
|
|
29823
|
+
tree = await full();
|
|
29824
|
+
if (!tree)
|
|
29825
|
+
return null;
|
|
29826
|
+
}
|
|
29827
|
+
const differs = await git(wtPath, "diff", "--name-only", "--no-renames", "-z", head, tree);
|
|
29828
|
+
if (differs.ok)
|
|
29829
|
+
synced.set(wtPath, { index, head, dirty: new Set(differs.out.split("\x00").filter(Boolean)) });
|
|
29830
|
+
return tree;
|
|
29831
|
+
}
|
|
29832
|
+
async function snapshotAt(wtPath, ref) {
|
|
29833
|
+
const r = await git(wtPath, "rev-parse", "--quiet", `${ref}^{commit}`, `${ref}^{tree}`, `${ref}^`);
|
|
29834
|
+
const [commit, tree, parent] = r.ok ? r.out.split(`
|
|
29835
|
+
`) : [];
|
|
29836
|
+
return commit && tree && parent ? { commit, tree, parent } : null;
|
|
29837
|
+
}
|
|
29838
|
+
function markTurn(wtPath, worktreeId) {
|
|
29839
|
+
return inTurn(wtPath, async () => {
|
|
29840
|
+
const head = await git(wtPath, "rev-parse", "--verify", "HEAD");
|
|
29841
|
+
if (!head.ok)
|
|
29842
|
+
return;
|
|
29843
|
+
const tree = await treeNow(wtPath, head.out);
|
|
29844
|
+
if (!tree)
|
|
29845
|
+
return;
|
|
29846
|
+
const at = await snapshotAt(wtPath, turnRef(worktreeId, "at"));
|
|
29847
|
+
if (at?.tree === tree && at.parent === head.out)
|
|
29848
|
+
return;
|
|
29849
|
+
const commit = await git(wtPath, "-c", "user.name=toyon", "-c", "user.email=toyon@localhost", "commit-tree", tree, "-p", head.out, "-m", SNAPSHOT_SUBJECT2);
|
|
29850
|
+
if (!commit.ok)
|
|
29851
|
+
return;
|
|
29852
|
+
if (at && at.tree !== tree)
|
|
29853
|
+
await git(wtPath, "update-ref", turnRef(worktreeId, "before"), at.commit);
|
|
29854
|
+
await git(wtPath, "update-ref", turnRef(worktreeId, "at"), commit.out);
|
|
29855
|
+
});
|
|
29856
|
+
}
|
|
29857
|
+
function turnChanges(wtPath, worktreeId, defaultBr, own, dirty) {
|
|
29858
|
+
return inTurn(wtPath, async () => {
|
|
29859
|
+
const at = await snapshotAt(wtPath, turnRef(worktreeId, "at"));
|
|
29860
|
+
if (!at)
|
|
29861
|
+
return null;
|
|
29862
|
+
const head = await git(wtPath, "rev-parse", "HEAD", "HEAD^{tree}");
|
|
29863
|
+
const [headCommit, headTree] = head.ok ? head.out.split(`
|
|
29864
|
+
`) : [];
|
|
29865
|
+
if (!headCommit)
|
|
29866
|
+
return null;
|
|
29867
|
+
const tree = await treeNow(wtPath, headCommit, dirty);
|
|
29868
|
+
if (!tree)
|
|
29869
|
+
return null;
|
|
29870
|
+
const from = at.tree !== tree ? at : await snapshotAt(wtPath, turnRef(worktreeId, "before"));
|
|
29871
|
+
if (!from || from.tree === tree || from.tree === headTree)
|
|
29872
|
+
return null;
|
|
29873
|
+
if (from.parent !== headCommit) {
|
|
29874
|
+
if (!own)
|
|
29875
|
+
return null;
|
|
29876
|
+
const [then, now] = await Promise.all([
|
|
29877
|
+
git(wtPath, "merge-base", from.parent, defaultBr),
|
|
29878
|
+
git(wtPath, "merge-base", headCommit, defaultBr)
|
|
29879
|
+
]);
|
|
29880
|
+
if (!then.ok || !now.ok || then.out !== now.out)
|
|
29881
|
+
return null;
|
|
29882
|
+
}
|
|
29883
|
+
const files = await filesBetween(wtPath, from.commit, tree);
|
|
29884
|
+
return files.length > 0 ? { base: from.commit, files } : null;
|
|
29885
|
+
});
|
|
29886
|
+
}
|
|
29887
|
+
async function dropTurnRefs(repoPath, worktreeId) {
|
|
29888
|
+
for (const which of ["at", "before"])
|
|
29889
|
+
await git(repoPath, "update-ref", "-d", turnRef(worktreeId, which));
|
|
29890
|
+
}
|
|
29891
|
+
|
|
29035
29892
|
// ../daemon/src/worktrees/archive.ts
|
|
29036
|
-
import { existsSync as
|
|
29037
|
-
import { basename as basename8, dirname as dirname10, join as
|
|
29893
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync17, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
29894
|
+
import { basename as basename8, dirname as dirname10, join as join25 } from "path";
|
|
29038
29895
|
class WorktreeArchive {
|
|
29039
29896
|
dir;
|
|
29040
29897
|
records = new Map;
|
|
29041
29898
|
constructor(dir) {
|
|
29042
29899
|
this.dir = dir;
|
|
29043
|
-
|
|
29044
|
-
for (const id of
|
|
29900
|
+
mkdirSync8(dir, { recursive: true });
|
|
29901
|
+
for (const id of readdirSync8(dir)) {
|
|
29045
29902
|
const file = this.filesOf(id).record;
|
|
29046
|
-
if (!
|
|
29903
|
+
if (!existsSync27(file))
|
|
29047
29904
|
continue;
|
|
29048
29905
|
try {
|
|
29049
29906
|
this.records.set(id, JSON.parse(readFileSync17(file, "utf8")));
|
|
@@ -29057,7 +29914,7 @@ class WorktreeArchive {
|
|
|
29057
29914
|
}
|
|
29058
29915
|
put(rec, from) {
|
|
29059
29916
|
const at = this.filesOf(rec.worktree.id);
|
|
29060
|
-
|
|
29917
|
+
mkdirSync8(at.dir, { recursive: true });
|
|
29061
29918
|
move(from.transcript, at.transcript);
|
|
29062
29919
|
move(from.attachments, at.attachments);
|
|
29063
29920
|
move(from.plans, at.plans);
|
|
@@ -29077,7 +29934,7 @@ class WorktreeArchive {
|
|
|
29077
29934
|
delete(id) {
|
|
29078
29935
|
if (!this.records.has(id))
|
|
29079
29936
|
return;
|
|
29080
|
-
|
|
29937
|
+
rmSync6(this.filesOf(id).dir, { recursive: true, force: true });
|
|
29081
29938
|
this.records.delete(id);
|
|
29082
29939
|
}
|
|
29083
29940
|
list(repo) {
|
|
@@ -29092,8 +29949,8 @@ class WorktreeArchive {
|
|
|
29092
29949
|
planFile(id, rel) {
|
|
29093
29950
|
if (!this.records.has(id) || !isPlanPath(rel))
|
|
29094
29951
|
return null;
|
|
29095
|
-
const file =
|
|
29096
|
-
if (!
|
|
29952
|
+
const file = join25(this.filesOf(id).plans, basename8(rel));
|
|
29953
|
+
if (!existsSync27(file))
|
|
29097
29954
|
return null;
|
|
29098
29955
|
try {
|
|
29099
29956
|
return readFileSync17(file, "utf8");
|
|
@@ -29103,13 +29960,13 @@ class WorktreeArchive {
|
|
|
29103
29960
|
}
|
|
29104
29961
|
}
|
|
29105
29962
|
filesOf(id) {
|
|
29106
|
-
const dir =
|
|
29963
|
+
const dir = join25(this.dir, id);
|
|
29107
29964
|
return {
|
|
29108
29965
|
dir,
|
|
29109
|
-
record:
|
|
29110
|
-
transcript:
|
|
29111
|
-
attachments:
|
|
29112
|
-
plans:
|
|
29966
|
+
record: join25(dir, "record.json"),
|
|
29967
|
+
transcript: join25(dir, "transcript.jsonl"),
|
|
29968
|
+
attachments: join25(dir, "attachments"),
|
|
29969
|
+
plans: join25(dir, "plans")
|
|
29113
29970
|
};
|
|
29114
29971
|
}
|
|
29115
29972
|
}
|
|
@@ -29134,7 +29991,7 @@ function summarize(r, repoId, transcript) {
|
|
|
29134
29991
|
};
|
|
29135
29992
|
}
|
|
29136
29993
|
function lastUsage(transcript) {
|
|
29137
|
-
if (!
|
|
29994
|
+
if (!existsSync27(transcript))
|
|
29138
29995
|
return null;
|
|
29139
29996
|
const lines = readFileSync17(transcript, "utf8").split(`
|
|
29140
29997
|
`);
|
|
@@ -29150,7 +30007,7 @@ function lastUsage(transcript) {
|
|
|
29150
30007
|
return null;
|
|
29151
30008
|
}
|
|
29152
30009
|
function firstPrompt(transcript) {
|
|
29153
|
-
if (!
|
|
30010
|
+
if (!existsSync27(transcript))
|
|
29154
30011
|
return;
|
|
29155
30012
|
for (const line of readFileSync17(transcript, "utf8").split(`
|
|
29156
30013
|
`)) {
|
|
@@ -29165,10 +30022,10 @@ function firstPrompt(transcript) {
|
|
|
29165
30022
|
return;
|
|
29166
30023
|
}
|
|
29167
30024
|
function move(from, to) {
|
|
29168
|
-
if (!
|
|
30025
|
+
if (!existsSync27(from))
|
|
29169
30026
|
return;
|
|
29170
|
-
|
|
29171
|
-
|
|
30027
|
+
mkdirSync8(dirname10(to), { recursive: true });
|
|
30028
|
+
rmSync6(to, { recursive: true, force: true });
|
|
29172
30029
|
renameSync4(from, to);
|
|
29173
30030
|
}
|
|
29174
30031
|
|
|
@@ -29230,8 +30087,8 @@ class ArchivedGit {
|
|
|
29230
30087
|
}
|
|
29231
30088
|
|
|
29232
30089
|
// ../daemon/src/worktrees/spare.ts
|
|
29233
|
-
import { existsSync as
|
|
29234
|
-
import { basename as basename9, join as
|
|
30090
|
+
import { existsSync as existsSync28 } from "fs";
|
|
30091
|
+
import { basename as basename9, join as join26 } from "path";
|
|
29235
30092
|
class SparePool {
|
|
29236
30093
|
d;
|
|
29237
30094
|
inflight = new Map;
|
|
@@ -29250,7 +30107,7 @@ class SparePool {
|
|
|
29250
30107
|
if (!text.trim())
|
|
29251
30108
|
return;
|
|
29252
30109
|
const wt = this.d.state.worktree(boxId);
|
|
29253
|
-
if (wt?.kind !== "spare" || !
|
|
30110
|
+
if (wt?.kind !== "spare" || !existsSync28(wt.path))
|
|
29254
30111
|
return;
|
|
29255
30112
|
const agent = this.d.runtime.ensureAgent(wt).agent;
|
|
29256
30113
|
if (agent.runningAgent === null)
|
|
@@ -29295,11 +30152,11 @@ class SparePool {
|
|
|
29295
30152
|
return this.record(repoId, repo).then((wt) => this.warmUp(wt, repo, {}));
|
|
29296
30153
|
}
|
|
29297
30154
|
async record(repoId, repo) {
|
|
29298
|
-
const { id, dir } = freeSlot(
|
|
30155
|
+
const { id, dir } = freeSlot(join26(this.d.paths.worktreesDir, repo.name));
|
|
29299
30156
|
const wt = {
|
|
29300
30157
|
id,
|
|
29301
30158
|
repoId,
|
|
29302
|
-
path:
|
|
30159
|
+
path: join26(this.d.paths.worktreesDir, repo.name, dir),
|
|
29303
30160
|
branch: repo.defaultBranch,
|
|
29304
30161
|
kind: "spare",
|
|
29305
30162
|
phase: "reserved",
|
|
@@ -29314,7 +30171,7 @@ class SparePool {
|
|
|
29314
30171
|
const repoId = repo.id;
|
|
29315
30172
|
let cut = () => {};
|
|
29316
30173
|
const added = (async () => {
|
|
29317
|
-
if (
|
|
30174
|
+
if (existsSync28(wt.path))
|
|
29318
30175
|
return;
|
|
29319
30176
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", wt.path, baseOf(repo)));
|
|
29320
30177
|
})();
|
|
@@ -29362,7 +30219,7 @@ class SparePool {
|
|
|
29362
30219
|
}
|
|
29363
30220
|
log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
|
|
29364
30221
|
this.d.carryDraft?.(wt.id, wt.repoId);
|
|
29365
|
-
if (
|
|
30222
|
+
if (existsSync28(wt.path)) {
|
|
29366
30223
|
await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
|
|
29367
30224
|
return;
|
|
29368
30225
|
}
|
|
@@ -29419,7 +30276,7 @@ class SparePool {
|
|
|
29419
30276
|
const running = this.inflight.get(repoId);
|
|
29420
30277
|
await running?.added.catch(() => {});
|
|
29421
30278
|
running?.cut();
|
|
29422
|
-
if (wt.kind !== "spare" || !
|
|
30279
|
+
if (wt.kind !== "spare" || !existsSync28(wt.path))
|
|
29423
30280
|
return null;
|
|
29424
30281
|
}
|
|
29425
30282
|
await this.refreshing.get(repoId);
|
|
@@ -29465,7 +30322,7 @@ class Trunk {
|
|
|
29465
30322
|
const last = this.lastFetch.get(path) ?? 0;
|
|
29466
30323
|
if (Date.now() - last > FETCH_EVERY_MS) {
|
|
29467
30324
|
this.lastFetch.set(path, Date.now());
|
|
29468
|
-
fireAndForget("fetch", run(GIT, ["fetch", "--quiet"], path, NO_PROMPT).then(async (r) => {
|
|
30325
|
+
fireAndForget("fetch", withFetchLock(path, () => run(GIT, ["fetch", "--quiet"], path, NO_PROMPT)).then(async (r) => {
|
|
29469
30326
|
const main = this.d.state.worktree(mainId);
|
|
29470
30327
|
if (main)
|
|
29471
30328
|
this.noteFetch(main.repoId, r);
|
|
@@ -29490,7 +30347,7 @@ class Trunk {
|
|
|
29490
30347
|
if (Date.now() - last < OPEN_FETCH_MIN_MS)
|
|
29491
30348
|
return;
|
|
29492
30349
|
this.lastFetch.set(repo.path, Date.now());
|
|
29493
|
-
const f = await run(GIT, ["fetch", "--quiet"], repo.path, NO_PROMPT);
|
|
30350
|
+
const f = await withFetchLock(repo.path, () => run(GIT, ["fetch", "--quiet"], repo.path, NO_PROMPT));
|
|
29494
30351
|
this.noteFetch(repo.id, f);
|
|
29495
30352
|
if (!f.ok) {
|
|
29496
30353
|
log.warn("fetch", `could not fetch ${repo.path}: ${f.err.slice(0, 200)}`);
|
|
@@ -29514,7 +30371,7 @@ class Trunk {
|
|
|
29514
30371
|
return { ok: false, message: "no main checkout to pull" };
|
|
29515
30372
|
this.lastFetch.set(repo.path, Date.now());
|
|
29516
30373
|
w.step(`pulling ${repo.defaultBranch} from origin`);
|
|
29517
|
-
const f = await w.git(repo.path, ["fetch", "--quiet"]);
|
|
30374
|
+
const f = await withFetchLock(repo.path, () => w.git(repo.path, ["fetch", "--quiet"]));
|
|
29518
30375
|
this.noteFetch(repo.id, f);
|
|
29519
30376
|
if (!f.ok)
|
|
29520
30377
|
return { ok: false, message: refused("fetch failed", f) };
|
|
@@ -29716,10 +30573,9 @@ class WorktreeService {
|
|
|
29716
30573
|
await this.discardWorktree(id);
|
|
29717
30574
|
},
|
|
29718
30575
|
carryDraft: (fromId, repoId) => {
|
|
29719
|
-
const text = this.d.drafts?.text(fromId);
|
|
29720
30576
|
const main = this.mainOf(repoId);
|
|
29721
|
-
if (
|
|
29722
|
-
this.d.drafts?.
|
|
30577
|
+
if (main)
|
|
30578
|
+
this.d.drafts?.move(fromId, main.id, true);
|
|
29723
30579
|
},
|
|
29724
30580
|
ready: (repoId) => {
|
|
29725
30581
|
const main = this.mainOf(repoId);
|
|
@@ -29728,15 +30584,15 @@ class WorktreeService {
|
|
|
29728
30584
|
if (this.d.runtime.get(main.id)?.procs) {
|
|
29729
30585
|
fireAndForget(main.id, this.d.runtime.stopPage(main.id), "main's page stops for the spare");
|
|
29730
30586
|
}
|
|
29731
|
-
const text = this.d.drafts?.text(main.id);
|
|
29732
30587
|
const spare = this.leadSpareOf(repoId);
|
|
29733
|
-
if (
|
|
29734
|
-
this.d.drafts?.
|
|
29735
|
-
this.d.drafts?.drop(main.id);
|
|
29736
|
-
}
|
|
30588
|
+
if (spare)
|
|
30589
|
+
this.d.drafts?.move(main.id, spare);
|
|
29737
30590
|
}
|
|
29738
30591
|
});
|
|
29739
30592
|
d.hub.on("agent", (worktreeId, _seq, event) => {
|
|
30593
|
+
if ((event.type === "tool-start" || event.type === "tool-update" && event.kind) && isWriteTool({ name: event.name ?? "", kind: event.kind })) {
|
|
30594
|
+
this.startPreview(worktreeId);
|
|
30595
|
+
}
|
|
29740
30596
|
if (event.type !== "usage")
|
|
29741
30597
|
return;
|
|
29742
30598
|
const { used, size, cost } = event;
|
|
@@ -29801,14 +30657,14 @@ class WorktreeService {
|
|
|
29801
30657
|
rt.agent.send(agentPrompt, { context: withCarry(context, carried), attachments });
|
|
29802
30658
|
this.scheduleNaming(claimed, task, variant);
|
|
29803
30659
|
if (carried.unmoved)
|
|
29804
|
-
throw new UserError(carried.unmoved);
|
|
30660
|
+
throw new UserError(carried.unmoved, { delivered: true });
|
|
29805
30661
|
return claimed;
|
|
29806
30662
|
}
|
|
29807
30663
|
if (!await isGitRepo(repo.path)) {
|
|
29808
30664
|
throw new UserError(`git no longer reads ${repo.name} as a checkout, so there is nothing to start a worktree from. If \`git config core.bare\` says true there, \`git config --unset core.bare\` puts it back.`);
|
|
29809
30665
|
}
|
|
29810
|
-
const { id, dir } = freeSlot(
|
|
29811
|
-
const wtPath =
|
|
30666
|
+
const { id, dir } = freeSlot(join27(this.d.paths.worktreesDir, repo.name));
|
|
30667
|
+
const wtPath = join27(this.d.paths.worktreesDir, repo.name, dir);
|
|
29812
30668
|
const branch = `toyon/${dir}`;
|
|
29813
30669
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
|
|
29814
30670
|
const wt = {
|
|
@@ -29835,7 +30691,7 @@ class WorktreeService {
|
|
|
29835
30691
|
this.d.runtime.ensureAgent(wt).agent.send(agentPrompt, { context: withCarry(context, carried), attachments });
|
|
29836
30692
|
this.scheduleNaming(wt, task, variant);
|
|
29837
30693
|
if (carried.unmoved)
|
|
29838
|
-
throw new UserError(carried.unmoved);
|
|
30694
|
+
throw new UserError(carried.unmoved, { delivered: true });
|
|
29839
30695
|
return wt;
|
|
29840
30696
|
}
|
|
29841
30697
|
async carryMain(repo, wt, move) {
|
|
@@ -29874,7 +30730,7 @@ class WorktreeService {
|
|
|
29874
30730
|
};
|
|
29875
30731
|
const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
|
|
29876
30732
|
const restored = (carried.ok ? carried.out.split(`
|
|
29877
|
-
`).filter(Boolean) : []).filter((p) => !
|
|
30733
|
+
`).filter(Boolean) : []).filter((p) => !existsSync29(join27(wt.path, p)));
|
|
29878
30734
|
const applied = await git(wt.path, "stash", "apply", sha);
|
|
29879
30735
|
if (applied.ok) {
|
|
29880
30736
|
const ref = await entry();
|
|
@@ -29910,9 +30766,9 @@ class WorktreeService {
|
|
|
29910
30766
|
const branch = kind === "pr" ? `pr/${number}` : ref;
|
|
29911
30767
|
const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
|
|
29912
30768
|
let slug = branchSlug(title) || "branch";
|
|
29913
|
-
if (
|
|
30769
|
+
if (existsSync29(join27(this.d.paths.worktreesDir, repo.name, slug)))
|
|
29914
30770
|
slug = `${slug}-${shortId().slice(0, 4)}`;
|
|
29915
|
-
const wtPath =
|
|
30771
|
+
const wtPath = join27(this.d.paths.worktreesDir, repo.name, slug);
|
|
29916
30772
|
await withRepoLock(repo.path, async () => {
|
|
29917
30773
|
const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
|
|
29918
30774
|
if (out)
|
|
@@ -30132,7 +30988,7 @@ class WorktreeService {
|
|
|
30132
30988
|
try {
|
|
30133
30989
|
await this.removeDirectory(repo, wt);
|
|
30134
30990
|
} catch (e) {
|
|
30135
|
-
|
|
30991
|
+
rmSync7(this.plansScratch(wt.id), { recursive: true, force: true });
|
|
30136
30992
|
throw e;
|
|
30137
30993
|
}
|
|
30138
30994
|
if (hasOwnBranch(wt)) {
|
|
@@ -30146,6 +31002,7 @@ class WorktreeService {
|
|
|
30146
31002
|
this.d.state.removeWorktree(worktreeId);
|
|
30147
31003
|
releasePort(wt.proxyPort);
|
|
30148
31004
|
let archived = null;
|
|
31005
|
+
await dropTurnRefs(repo.path, worktreeId);
|
|
30149
31006
|
if (archive)
|
|
30150
31007
|
archived = this.archiveChat(wt, repo, kept, sessionId, reason);
|
|
30151
31008
|
else {
|
|
@@ -30158,7 +31015,7 @@ class WorktreeService {
|
|
|
30158
31015
|
}
|
|
30159
31016
|
async teardown(wt, repo) {
|
|
30160
31017
|
const cmds = repo.config.teardown ?? [];
|
|
30161
|
-
if (cmds.length === 0 || !
|
|
31018
|
+
if (cmds.length === 0 || !existsSync29(wt.path))
|
|
30162
31019
|
return;
|
|
30163
31020
|
for (const cmd of cmds) {
|
|
30164
31021
|
const code = await runSetup(cmd, wt.path, (line) => this.d.hub.emit("log", wt.id, "setup", line), worktreeEnv(wt, repo), { timeoutMs: timeoutFor(repo.config, "setup") });
|
|
@@ -30167,7 +31024,7 @@ class WorktreeService {
|
|
|
30167
31024
|
}
|
|
30168
31025
|
}
|
|
30169
31026
|
async keep(repo, wt) {
|
|
30170
|
-
const index =
|
|
31027
|
+
const index = join27(this.d.paths.archiveDir, `${wt.id}.index`);
|
|
30171
31028
|
const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
|
|
30172
31029
|
if (!kept)
|
|
30173
31030
|
log.warn(wt.id, "could not keep its git state");
|
|
@@ -30204,11 +31061,11 @@ class WorktreeService {
|
|
|
30204
31061
|
log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
|
|
30205
31062
|
}
|
|
30206
31063
|
plansScratch(worktreeId) {
|
|
30207
|
-
return
|
|
31064
|
+
return join27(this.d.paths.archiveDir, `${worktreeId}.plans`);
|
|
30208
31065
|
}
|
|
30209
31066
|
keepPlans(wt) {
|
|
30210
|
-
const from =
|
|
30211
|
-
if (!
|
|
31067
|
+
const from = join27(wt.path, PLANS_DIR);
|
|
31068
|
+
if (!existsSync29(from))
|
|
30212
31069
|
return;
|
|
30213
31070
|
try {
|
|
30214
31071
|
cpSync(from, this.plansScratch(wt.id), { recursive: true });
|
|
@@ -30249,8 +31106,8 @@ class WorktreeService {
|
|
|
30249
31106
|
deleteChat(worktreeId) {
|
|
30250
31107
|
const files = this.chatFiles(worktreeId);
|
|
30251
31108
|
try {
|
|
30252
|
-
|
|
30253
|
-
|
|
31109
|
+
rmSync7(files.transcript, { force: true });
|
|
31110
|
+
rmSync7(files.attachments, { recursive: true, force: true });
|
|
30254
31111
|
} catch (e) {
|
|
30255
31112
|
log.warn(worktreeId, "could not delete transcript or attachments", e);
|
|
30256
31113
|
}
|
|
@@ -30296,7 +31153,7 @@ class WorktreeService {
|
|
|
30296
31153
|
}
|
|
30297
31154
|
archivedAttachment(archiveId, file) {
|
|
30298
31155
|
const files = this.archive.chatFiles(archiveId);
|
|
30299
|
-
return files && isAttachmentFile(file) ?
|
|
31156
|
+
return files && isAttachmentFile(file) ? join27(files.attachments, file) : null;
|
|
30300
31157
|
}
|
|
30301
31158
|
archivedGit(archiveId) {
|
|
30302
31159
|
const rec = this.archive.get(archiveId);
|
|
@@ -30339,7 +31196,7 @@ class WorktreeService {
|
|
|
30339
31196
|
throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
|
|
30340
31197
|
if (rec.auto)
|
|
30341
31198
|
log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
|
|
30342
|
-
const path =
|
|
31199
|
+
const path = existsSync29(old.path) ? join27(dirname11(old.path), freeSlot(dirname11(old.path)).dir) : old.path;
|
|
30343
31200
|
const branch = await withRepoLock(repo.path, async () => {
|
|
30344
31201
|
if (!await commitOf(repo.path, archiveRef(old.id))) {
|
|
30345
31202
|
throw new UserError(`${old.title}'s kept commits are gone from git`);
|
|
@@ -30373,10 +31230,11 @@ class WorktreeService {
|
|
|
30373
31230
|
branch,
|
|
30374
31231
|
proxyPort: await allocateProxyPort(),
|
|
30375
31232
|
promptedAt: Date.now(),
|
|
31233
|
+
parked: true,
|
|
30376
31234
|
...createdBy ? { createdBy } : {}
|
|
30377
31235
|
};
|
|
30378
31236
|
try {
|
|
30379
|
-
this.archive.take(old.id, { ...this.chatFiles(old.id), plans:
|
|
31237
|
+
this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join27(path, PLANS_DIR) });
|
|
30380
31238
|
} catch (e) {
|
|
30381
31239
|
log.warn(old.id, "could not move its chat back out of the archive", e);
|
|
30382
31240
|
}
|
|
@@ -30405,12 +31263,12 @@ class WorktreeService {
|
|
|
30405
31263
|
return;
|
|
30406
31264
|
const repo = this.repoOf(rec);
|
|
30407
31265
|
const repoPath = repo?.path ?? rec.repoPath;
|
|
30408
|
-
if (rec.kept &&
|
|
31266
|
+
if (rec.kept && existsSync29(repoPath)) {
|
|
30409
31267
|
const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
|
|
30410
31268
|
if (!r.ok)
|
|
30411
31269
|
log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
|
|
30412
31270
|
}
|
|
30413
|
-
if (rec.worktree.lands?.length &&
|
|
31271
|
+
if (rec.worktree.lands?.length && existsSync29(repoPath))
|
|
30414
31272
|
await dropLandRefs(repoPath, archiveId);
|
|
30415
31273
|
this.archive.delete(archiveId);
|
|
30416
31274
|
this.d.drafts?.drop(archiveId);
|
|
@@ -30501,6 +31359,15 @@ class WorktreeService {
|
|
|
30501
31359
|
this.d.hub.emit("worktreesChanged");
|
|
30502
31360
|
return { target, grafted: sources.map((w) => w.title) };
|
|
30503
31361
|
}
|
|
31362
|
+
startPreview(worktreeId) {
|
|
31363
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
31364
|
+
if (!wt?.parked)
|
|
31365
|
+
return;
|
|
31366
|
+
delete wt.parked;
|
|
31367
|
+
this.d.state.save();
|
|
31368
|
+
this.d.hub.emit("worktreesChanged");
|
|
31369
|
+
fireAndForget(worktreeId, this.d.runtime.wake(worktreeId), "start a parked preview");
|
|
31370
|
+
}
|
|
30504
31371
|
async setupAndStart(wt, repo, depsSource = repo.path, opts = {}) {
|
|
30505
31372
|
this.d.runtime.markSetup(wt.id, true);
|
|
30506
31373
|
try {
|
|
@@ -30510,9 +31377,9 @@ class WorktreeService {
|
|
|
30510
31377
|
}
|
|
30511
31378
|
}
|
|
30512
31379
|
async mirrorBaseIgnore(repo) {
|
|
30513
|
-
const src =
|
|
30514
|
-
const tracked =
|
|
30515
|
-
const mirror =
|
|
31380
|
+
const src = join27(repo.path, ".gitignore");
|
|
31381
|
+
const tracked = existsSync29(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
|
|
31382
|
+
const mirror = existsSync29(src) && !tracked;
|
|
30516
31383
|
const lines = mirror ? readFileSync18(src, "utf8").split(`
|
|
30517
31384
|
`).filter((l) => l.trim() !== "") : [];
|
|
30518
31385
|
await excludeBlock(repo.path, BASE_IGNORE, lines);
|
|
@@ -30525,9 +31392,9 @@ class WorktreeService {
|
|
|
30525
31392
|
await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
|
|
30526
31393
|
run.finish(0);
|
|
30527
31394
|
}
|
|
30528
|
-
const srcNm =
|
|
30529
|
-
const dstNm =
|
|
30530
|
-
if (
|
|
31395
|
+
const srcNm = join27(depsSource, "node_modules");
|
|
31396
|
+
const dstNm = join27(wt.path, "node_modules");
|
|
31397
|
+
if (existsSync29(srcNm) && !existsSync29(dstNm)) {
|
|
30531
31398
|
const r = await cloneTree(srcNm, dstNm);
|
|
30532
31399
|
if (r.ok)
|
|
30533
31400
|
this.d.hub.emit("log", wt.id, "setup", `deps via ${r.how} in ${r.ms}ms`);
|
|
@@ -30535,10 +31402,10 @@ class WorktreeService {
|
|
|
30535
31402
|
log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
|
|
30536
31403
|
}
|
|
30537
31404
|
for (const f of LOCAL_CONFIG_FILES) {
|
|
30538
|
-
const src =
|
|
30539
|
-
if (!
|
|
31405
|
+
const src = join27(depsSource, f);
|
|
31406
|
+
if (!existsSync29(src) || existsSync29(join27(wt.path, f)))
|
|
30540
31407
|
continue;
|
|
30541
|
-
const r = await run("cp", [src,
|
|
31408
|
+
const r = await run("cp", [src, join27(wt.path, f)], wt.path);
|
|
30542
31409
|
if (r.ok)
|
|
30543
31410
|
this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
|
|
30544
31411
|
else
|
|
@@ -30706,12 +31573,24 @@ ${wt.landing.body}` : s;
|
|
|
30706
31573
|
const { wt, repo } = this.landable(worktreeId, "land");
|
|
30707
31574
|
const out = await this.ship(wt.id, "land", async () => {
|
|
30708
31575
|
await this.followBranch(wt);
|
|
31576
|
+
const moved = await this.movedMidTurn(wt);
|
|
31577
|
+
if (moved)
|
|
31578
|
+
return { result: moved };
|
|
30709
31579
|
return this.landOp(wt, repo, message);
|
|
30710
31580
|
});
|
|
30711
31581
|
if (out.result.ok && out.archiveIds)
|
|
30712
31582
|
this.noteLanded(wt, out.result.message, out.archiveIds);
|
|
30713
31583
|
return out;
|
|
30714
31584
|
}
|
|
31585
|
+
async movedMidTurn(wt) {
|
|
31586
|
+
const status = this.d.runtime.agentFor(wt.id)?.status;
|
|
31587
|
+
if (status !== "working" && status !== "waiting")
|
|
31588
|
+
return null;
|
|
31589
|
+
const l = wt.landing;
|
|
31590
|
+
if (!l || l.fingerprint === await treeFingerprint(wt.path))
|
|
31591
|
+
return null;
|
|
31592
|
+
return { ok: false, message: "the agent has changed files since the check: land once its turn ends" };
|
|
31593
|
+
}
|
|
30715
31594
|
noteLanded(wt, message, archiveIds) {
|
|
30716
31595
|
this.d.runtime.ensureAgent(wt).agent.note({ type: "landed", message, archiveIds, ts: Date.now() });
|
|
30717
31596
|
}
|
|
@@ -30934,7 +31813,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
30934
31813
|
async fetchBase(wt, repo, w) {
|
|
30935
31814
|
if (!baseIsRemote(repo))
|
|
30936
31815
|
return { ok: true, message: `${baseOf(repo)} here is the base` };
|
|
30937
|
-
const r = await fetchBase(wt.path, repo, w);
|
|
31816
|
+
const r = await withFetchLock(repo.path, () => fetchBase(wt.path, repo, w));
|
|
30938
31817
|
this.trunk.noteFetch(repo.id, { ok: r.ok, err: r.message });
|
|
30939
31818
|
return r;
|
|
30940
31819
|
}
|
|
@@ -30983,6 +31862,10 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
30983
31862
|
throw new UserError(`${r.name} is held by another tool`);
|
|
30984
31863
|
const repo = this.d.state.requireRepo(r.repoId);
|
|
30985
31864
|
const own = r.wt ? hasOwnBranch(r.wt) : false;
|
|
31865
|
+
const carry = !!r.wt;
|
|
31866
|
+
const status = this.d.runtime.agentFor(worktreeId)?.status;
|
|
31867
|
+
if (carry && (status === "working" || status === "waiting") && (await statusFiles(r.path)).length > 0)
|
|
31868
|
+
throw new UserError(`${r.name}'s agent is mid-turn with uncommitted changes: sync when it is done`);
|
|
30986
31869
|
const w = r.wt ? this.landWatch(r.wt) : UNWATCHED;
|
|
30987
31870
|
const result = await this.ship(worktreeId, "sync-main", async () => {
|
|
30988
31871
|
if (r.wt) {
|
|
@@ -30990,7 +31873,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
30990
31873
|
if (!fetched.ok)
|
|
30991
31874
|
return fetched;
|
|
30992
31875
|
}
|
|
30993
|
-
const taken = await withRepoLock(repo.path, () => takeMainIn(r.path, r.base, own, w));
|
|
31876
|
+
const taken = await withRepoLock(repo.path, () => takeMainIn(r.path, r.base, own, w, carry));
|
|
30994
31877
|
if (taken.ok) {
|
|
30995
31878
|
this.headMoved(worktreeId);
|
|
30996
31879
|
if (r.wt)
|
|
@@ -31166,6 +32049,11 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31166
32049
|
base: baseOf(repo)
|
|
31167
32050
|
};
|
|
31168
32051
|
}
|
|
32052
|
+
async turnStarted(worktreeId) {
|
|
32053
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
32054
|
+
if (wt && !this.going.has(worktreeId))
|
|
32055
|
+
await markTurn(wt.path, wt.id);
|
|
32056
|
+
}
|
|
31169
32057
|
async gitStatus(worktreeId) {
|
|
31170
32058
|
if (this.going.has(worktreeId))
|
|
31171
32059
|
return null;
|
|
@@ -31186,6 +32074,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31186
32074
|
const counts = { ...ab, ...unpushed === undefined ? {} : { unpushed } };
|
|
31187
32075
|
const ahead = counts.ahead ?? 0;
|
|
31188
32076
|
const committed = !isMain && ahead > 0 ? await committedFiles(r.path, r.base) : undefined;
|
|
32077
|
+
const turn = r.wt && files.length > 0 ? await turnChanges(r.path, r.wt.id, r.base, !isMain, files) : null;
|
|
31189
32078
|
if (r.wt && !isMain && hasOwnBranch(r.wt) && files.length === 0 && ahead === 0 && head.ok && this.handChecked.get(r.wt.id) !== head.out) {
|
|
31190
32079
|
this.handChecked.set(r.wt.id, head.out);
|
|
31191
32080
|
const mark = await handLanding(r.path, r.wt.branch, r.base, r.wt.lands?.at(-1)?.tip);
|
|
@@ -31212,7 +32101,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31212
32101
|
if (this.going.has(worktreeId))
|
|
31213
32102
|
return null;
|
|
31214
32103
|
this.noteCounts(worktreeId, isMain, files.length, counts);
|
|
31215
|
-
return { files, committed, head: head.ok ? head.out : undefined, ...counts };
|
|
32104
|
+
return { files, committed, ...turn ? { turn } : {}, head: head.ok ? head.out : undefined, ...counts };
|
|
31216
32105
|
} catch (e) {
|
|
31217
32106
|
log.warn(worktreeId, "git status failed", e);
|
|
31218
32107
|
return null;
|
|
@@ -31481,7 +32370,8 @@ var accounts = new AgentAccounts({
|
|
|
31481
32370
|
connect: async (app, spec) => spawnAcp(app, agents.launch(spec, await prepareLaunch(paths.scratchDir, spec)), paths.scratchDir, `auth:${spec.id}`)
|
|
31482
32371
|
});
|
|
31483
32372
|
accounts.onChange = () => hub.emit("agentsChanged");
|
|
31484
|
-
var
|
|
32373
|
+
var uploads = new UploadStore(paths.uploadsDir);
|
|
32374
|
+
var attachments2 = new AttachmentStore(paths.attachmentsDir, uploads);
|
|
31485
32375
|
var runtime = new RuntimeRegistry({
|
|
31486
32376
|
hub,
|
|
31487
32377
|
state,
|
|
@@ -31491,13 +32381,19 @@ var runtime = new RuntimeRegistry({
|
|
|
31491
32381
|
accounts,
|
|
31492
32382
|
attachments: attachments2,
|
|
31493
32383
|
bridgeScript: () => bridge.get(),
|
|
32384
|
+
turnStarting: (id) => worktrees.turnStarted(id),
|
|
31494
32385
|
remote,
|
|
31495
32386
|
grant: previewGrant(token),
|
|
31496
32387
|
viewed: (id) => idle.isViewed(id),
|
|
31497
32388
|
shown: (id) => idle.isShown(id),
|
|
31498
32389
|
mainLeads: (repoId) => worktrees.spare.current(repoId) === null
|
|
31499
32390
|
});
|
|
31500
|
-
var drafts = new DraftStore({
|
|
32391
|
+
var drafts = new DraftStore({
|
|
32392
|
+
file: paths.draftsFile,
|
|
32393
|
+
attachmentsFile: paths.draftAttachmentsFile,
|
|
32394
|
+
hub,
|
|
32395
|
+
uploads
|
|
32396
|
+
});
|
|
31501
32397
|
var runs = new RunService({ state, hub });
|
|
31502
32398
|
var exec = new ExecService({ state, runtime, shipping: (id) => worktrees.shippingOf(id) });
|
|
31503
32399
|
var cache = new ArtifactCache({ paths, state, hub });
|
|
@@ -31667,6 +32563,7 @@ var stopLagSampler = startLagSampler();
|
|
|
31667
32563
|
await runtime.reclaimOrphans();
|
|
31668
32564
|
await repos.boot();
|
|
31669
32565
|
drafts.prune((id) => !!state.worktree(id) || worktrees.hasArchived(id));
|
|
32566
|
+
uploads.sweep(drafts.uploadIds());
|
|
31670
32567
|
idle.boot();
|
|
31671
32568
|
runs.boot();
|
|
31672
32569
|
landing.boot();
|
|
@@ -31722,7 +32619,7 @@ async function shutdown(signal, opts = {}) {
|
|
|
31722
32619
|
const deadline = new Promise((resolve) => setTimeout(resolve, 5000));
|
|
31723
32620
|
await Promise.race([Promise.all([runtime.shutdown(), exec.stopAll()]), deadline]);
|
|
31724
32621
|
state.flushGroups();
|
|
31725
|
-
|
|
32622
|
+
rmSync8(paths.pidFile, { force: true });
|
|
31726
32623
|
if (opts.respawn) {
|
|
31727
32624
|
await Bun.sleep(250);
|
|
31728
32625
|
log.info("daemon", "starting the replacement");
|