toyon 0.8.1 → 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 +6 -3
- package/dist/daemon.js +1124 -394
- package/dist/shell/assets/{Editor-CrfFlcLA.js → Editor-DAJni9OF.js} +137 -137
- package/dist/shell/assets/{XTerm-CsuyFSMK.js → XTerm-dug6M6yd.js} +16 -16
- package/dist/shell/assets/{cssMode-CWkICf31.js → cssMode-B5iiwdXn.js} +1 -1
- package/dist/shell/assets/{freemarker2-C2fP12sE.js → freemarker2-CdVcBNE9.js} +1 -1
- package/dist/shell/assets/{grammar-5S_Kl1e8.js → grammar-Cfxx1yRn.js} +5 -5
- package/dist/shell/assets/{handlebars-CkRyqZGC.js → handlebars-CW5EFZC2.js} +1 -1
- package/dist/shell/assets/{html-C9iJKWKA.js → html-B8gP1xu-.js} +1 -1
- package/dist/shell/assets/{htmlMode-Dps-6NJd.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-DM5EiBY7.js → javascript-C_3_LFln.js} +1 -1
- package/dist/shell/assets/{jsonMode-ampRCTsW.js → jsonMode-CD5xJEUk.js} +1 -1
- package/dist/shell/assets/{liquid-CEUat1ZS.js → liquid-CJw93Zxn.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-ChupARNW.js → lspLanguageFeatures-wMR368iN.js} +1 -1
- package/dist/shell/assets/{mdx-DAglTq4w.js → mdx-BJ6yMgc3.js} +1 -1
- package/dist/shell/assets/{python-DOPtAG-1.js → python-BWGkxFUr.js} +1 -1
- package/dist/shell/assets/{razor-qRDOBjWc.js → razor-BV9pdU1A.js} +1 -1
- package/dist/shell/assets/{tsMode-BD49NPBt.js → tsMode-CN8OfYAX.js} +1 -1
- package/dist/shell/assets/{xml-p5Js1H7t.js → xml-DBGLqvw7.js} +1 -1
- package/dist/shell/assets/{yaml-CctH9qfh.js → yaml-dH0C6Dth.js} +1 -1
- package/dist/shell/index.html +21 -2
- package/package.json +1 -1
- package/dist/shell/assets/index-BKyLPsvB.js +0 -258
- package/dist/shell/assets/index-DIDGdnCY.css +0 -1
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({
|
|
@@ -8154,7 +8201,7 @@ function safeUser() {
|
|
|
8154
8201
|
// ../daemon/package.json
|
|
8155
8202
|
var package_default = {
|
|
8156
8203
|
name: "@toyon/daemon",
|
|
8157
|
-
version: "0.
|
|
8204
|
+
version: "0.9.0",
|
|
8158
8205
|
private: true,
|
|
8159
8206
|
type: "module",
|
|
8160
8207
|
main: "src/index.ts",
|
|
@@ -12248,9 +12295,11 @@ var legacyClientNotificationMethods = new Set([
|
|
|
12248
12295
|
|
|
12249
12296
|
// ../daemon/src/core/errors.ts
|
|
12250
12297
|
class UserError extends Error {
|
|
12251
|
-
|
|
12298
|
+
delivered;
|
|
12299
|
+
constructor(message, about = {}) {
|
|
12252
12300
|
super(message);
|
|
12253
12301
|
this.name = "UserError";
|
|
12302
|
+
this.delivered = about.delivered ?? false;
|
|
12254
12303
|
}
|
|
12255
12304
|
}
|
|
12256
12305
|
|
|
@@ -12503,18 +12552,177 @@ function spawnAcp(app, launch, cwd, tag) {
|
|
|
12503
12552
|
|
|
12504
12553
|
// ../daemon/src/agent/attachments.ts
|
|
12505
12554
|
import { createHash } from "crypto";
|
|
12506
|
-
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";
|
|
12507
12562
|
import { join } from "path";
|
|
12508
|
-
var
|
|
12563
|
+
var IMAGE_EXT = {
|
|
12509
12564
|
"image/png": "png",
|
|
12510
12565
|
"image/jpeg": "jpg",
|
|
12511
12566
|
"image/gif": "gif",
|
|
12512
12567
|
"image/webp": "webp"
|
|
12513
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
|
|
12514
12707
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12515
|
-
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
|
+
}
|
|
12516
12724
|
function toolImage(data, mimeType) {
|
|
12517
|
-
const ext =
|
|
12725
|
+
const ext = IMAGE_EXT[mimeType];
|
|
12518
12726
|
if (!ext)
|
|
12519
12727
|
return null;
|
|
12520
12728
|
const bytes = Buffer.from(data, "base64");
|
|
@@ -12523,27 +12731,40 @@ function toolImage(data, mimeType) {
|
|
|
12523
12731
|
const file = `${createHash("sha256").update(bytes).digest("hex").slice(0, 16)}.${ext}`;
|
|
12524
12732
|
return { ref: { file, mimeType, bytes: bytes.length }, bytes };
|
|
12525
12733
|
}
|
|
12526
|
-
var isAttachmentFile = (file) =>
|
|
12734
|
+
var isAttachmentFile = (file) => isFile(file);
|
|
12527
12735
|
function attachmentsDirFor(attachmentsDir, worktreeId) {
|
|
12528
|
-
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
|
+
}
|
|
12529
12748
|
}
|
|
12530
12749
|
|
|
12531
12750
|
class AttachmentStore {
|
|
12532
12751
|
dir;
|
|
12752
|
+
uploads;
|
|
12533
12753
|
pending = new Map;
|
|
12534
|
-
constructor(dir) {
|
|
12754
|
+
constructor(dir, uploads) {
|
|
12535
12755
|
this.dir = dir;
|
|
12756
|
+
this.uploads = uploads;
|
|
12536
12757
|
}
|
|
12537
12758
|
async putToolImage(worktreeId, file, bytes) {
|
|
12538
|
-
if (!ID.test(worktreeId) || !
|
|
12759
|
+
if (!ID.test(worktreeId) || !DRAWN.test(file))
|
|
12539
12760
|
throw new UserError("bad tool image");
|
|
12540
|
-
const path =
|
|
12761
|
+
const path = join2(attachmentsDirFor(this.dir, worktreeId), file);
|
|
12541
12762
|
if (this.pending.has(path) || await Bun.file(path).exists())
|
|
12542
12763
|
return;
|
|
12543
|
-
await this.write(worktreeId, file, bytes);
|
|
12764
|
+
await this.write(worktreeId, file, (path) => writeFile(path, bytes));
|
|
12544
12765
|
}
|
|
12545
12766
|
whenWritten(worktreeId, file) {
|
|
12546
|
-
return this.pending.get(
|
|
12767
|
+
return this.pending.get(join2(attachmentsDirFor(this.dir, worktreeId), file)) ?? Promise.resolve();
|
|
12547
12768
|
}
|
|
12548
12769
|
async put(worktreeId, n, input) {
|
|
12549
12770
|
if (!ID.test(worktreeId))
|
|
@@ -12553,19 +12774,23 @@ class AttachmentStore {
|
|
|
12553
12774
|
return this.putImage(worktreeId, n, input);
|
|
12554
12775
|
case "paste":
|
|
12555
12776
|
return this.putText(worktreeId, n, input);
|
|
12777
|
+
case "file":
|
|
12778
|
+
return this.putFile(worktreeId, n, input);
|
|
12556
12779
|
case "pick":
|
|
12557
12780
|
return { kind: "pick", ref: { ...input, n } };
|
|
12558
12781
|
}
|
|
12559
12782
|
}
|
|
12560
12783
|
async putImage(worktreeId, n, img) {
|
|
12561
|
-
const
|
|
12562
|
-
if (
|
|
12563
|
-
throw new UserError(`${img.name}:
|
|
12564
|
-
const
|
|
12565
|
-
|
|
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));
|
|
12566
12790
|
return {
|
|
12567
12791
|
kind: "image",
|
|
12568
12792
|
bytes,
|
|
12793
|
+
path,
|
|
12569
12794
|
ref: {
|
|
12570
12795
|
kind: "image",
|
|
12571
12796
|
n,
|
|
@@ -12578,28 +12803,78 @@ class AttachmentStore {
|
|
|
12578
12803
|
}
|
|
12579
12804
|
};
|
|
12580
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
|
+
}
|
|
12581
12821
|
async putText(worktreeId, n, { text, name, source }) {
|
|
12582
12822
|
if (text.length === 0)
|
|
12583
12823
|
throw new UserError("empty paste");
|
|
12584
12824
|
const file = `${n}.txt`;
|
|
12585
|
-
await this.write(worktreeId, file, text);
|
|
12825
|
+
await this.write(worktreeId, file, (to) => writeFile(to, text));
|
|
12586
12826
|
return {
|
|
12587
12827
|
kind: "paste",
|
|
12588
12828
|
text,
|
|
12589
12829
|
ref: { kind: "paste", n, ...name ? { name } : {}, ...source ? { source } : {}, ...pasteSummary(text), file }
|
|
12590
12830
|
};
|
|
12591
12831
|
}
|
|
12592
|
-
|
|
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) {
|
|
12593
12867
|
const wtDir = attachmentsDirFor(this.dir, worktreeId);
|
|
12594
|
-
const path =
|
|
12595
|
-
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));
|
|
12596
12870
|
this.pending.set(path, done);
|
|
12597
|
-
|
|
12871
|
+
await done;
|
|
12872
|
+
return path;
|
|
12598
12873
|
}
|
|
12599
12874
|
fileFor(worktreeId, file) {
|
|
12600
|
-
if (!ID.test(worktreeId) || !
|
|
12875
|
+
if (!ID.test(worktreeId) || !isFile(file))
|
|
12601
12876
|
return null;
|
|
12602
|
-
return
|
|
12877
|
+
return join2(attachmentsDirFor(this.dir, worktreeId), file);
|
|
12603
12878
|
}
|
|
12604
12879
|
}
|
|
12605
12880
|
|
|
@@ -12756,9 +13031,9 @@ class OptionProbe {
|
|
|
12756
13031
|
}
|
|
12757
13032
|
|
|
12758
13033
|
// ../daemon/src/agent/registry.ts
|
|
12759
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
13034
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
12760
13035
|
import { homedir as homedir3 } from "os";
|
|
12761
|
-
import { dirname as dirname3, isAbsolute, join as
|
|
13036
|
+
import { dirname as dirname3, isAbsolute, join as join6, resolve as resolve3 } from "path";
|
|
12762
13037
|
|
|
12763
13038
|
// ../daemon/src/core/cloud.ts
|
|
12764
13039
|
var enabled = process.env.TOYON_CLOUD === "1";
|
|
@@ -12785,7 +13060,7 @@ var cloud = {
|
|
|
12785
13060
|
import { spawn as spawn2 } from "child_process";
|
|
12786
13061
|
import { createHash as createHash2 } from "crypto";
|
|
12787
13062
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
12788
|
-
import { join as
|
|
13063
|
+
import { join as join3 } from "path";
|
|
12789
13064
|
|
|
12790
13065
|
// ../daemon/src/core/metrics.ts
|
|
12791
13066
|
var WARN_MS = 50;
|
|
@@ -12827,7 +13102,7 @@ function lockfileHash(dir) {
|
|
|
12827
13102
|
const h = createHash2("sha1");
|
|
12828
13103
|
for (const f of LOCKFILES) {
|
|
12829
13104
|
try {
|
|
12830
|
-
h.update(readFileSync2(
|
|
13105
|
+
h.update(readFileSync2(join3(dir, f)));
|
|
12831
13106
|
} catch {}
|
|
12832
13107
|
}
|
|
12833
13108
|
return h.digest("hex");
|
|
@@ -13142,7 +13417,7 @@ function confine(launch, b, extraWrite = [], platform = process.platform, which
|
|
|
13142
13417
|
}
|
|
13143
13418
|
|
|
13144
13419
|
// ../daemon/src/agent/native.ts
|
|
13145
|
-
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
13420
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
13146
13421
|
var OS = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
13147
13422
|
function nativePackage(pkg, host) {
|
|
13148
13423
|
const os = OS[host.platform];
|
|
@@ -13165,7 +13440,7 @@ function hostTraits() {
|
|
|
13165
13440
|
}
|
|
13166
13441
|
function muslLoader() {
|
|
13167
13442
|
try {
|
|
13168
|
-
return existsSync2("/lib") &&
|
|
13443
|
+
return existsSync2("/lib") && readdirSync2("/lib").some((f) => f.startsWith("ld-musl-"));
|
|
13169
13444
|
} catch {
|
|
13170
13445
|
return false;
|
|
13171
13446
|
}
|
|
@@ -13235,49 +13510,52 @@ function opencodeEnv() {
|
|
|
13235
13510
|
}
|
|
13236
13511
|
|
|
13237
13512
|
// ../daemon/src/agent/sandbox.ts
|
|
13238
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
13513
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
13239
13514
|
import { homedir as homedir2, tmpdir } from "os";
|
|
13240
|
-
import { join as
|
|
13515
|
+
import { join as join5, resolve as resolve2 } from "path";
|
|
13241
13516
|
|
|
13242
13517
|
// ../daemon/src/core/paths.ts
|
|
13243
|
-
import { mkdirSync } from "fs";
|
|
13518
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
13244
13519
|
import { homedir } from "os";
|
|
13245
|
-
import { join as
|
|
13246
|
-
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")) {
|
|
13247
13522
|
return {
|
|
13248
13523
|
home,
|
|
13249
|
-
stateFile:
|
|
13250
|
-
draftsFile:
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
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")
|
|
13265
13542
|
};
|
|
13266
13543
|
}
|
|
13267
13544
|
function ensureDirs(p) {
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
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 });
|
|
13277
13555
|
}
|
|
13278
13556
|
|
|
13279
13557
|
// ../daemon/src/git/exclude.ts
|
|
13280
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
13558
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
13281
13559
|
import { dirname } from "path";
|
|
13282
13560
|
async function excludeFile(cwd) {
|
|
13283
13561
|
const r = await git(cwd, "rev-parse", "--path-format=absolute", "--git-path", "info/exclude");
|
|
@@ -13294,7 +13572,7 @@ async function excludeFromGit(cwd, pattern) {
|
|
|
13294
13572
|
if (current.split(`
|
|
13295
13573
|
`).some((l) => l.trim() === pattern))
|
|
13296
13574
|
return;
|
|
13297
|
-
|
|
13575
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
13298
13576
|
writeFileSync(file, `${current}${current && !current.endsWith(`
|
|
13299
13577
|
`) ? `
|
|
13300
13578
|
` : ""}${pattern}
|
|
@@ -13320,7 +13598,7 @@ async function excludeBlock(cwd, name, patterns) {
|
|
|
13320
13598
|
` : "";
|
|
13321
13599
|
if (text === current)
|
|
13322
13600
|
return;
|
|
13323
|
-
|
|
13601
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
13324
13602
|
writeFileSync(file, text);
|
|
13325
13603
|
}
|
|
13326
13604
|
async function unexcludeFromGit(cwd, pattern) {
|
|
@@ -13387,7 +13665,7 @@ async function prepareLaunch(cwd, spec) {
|
|
|
13387
13665
|
log.warn(cwd, `agent ${spec.id} runs without an OS sandbox`);
|
|
13388
13666
|
return { bounds, env: setup?.env ?? {} };
|
|
13389
13667
|
}
|
|
13390
|
-
var SETTINGS_REL =
|
|
13668
|
+
var SETTINGS_REL = join5(".claude", "settings.local.json");
|
|
13391
13669
|
function claudeLocalSettings(b) {
|
|
13392
13670
|
return {
|
|
13393
13671
|
sandbox: {
|
|
@@ -13401,8 +13679,8 @@ function claudeLocalSettings(b) {
|
|
|
13401
13679
|
};
|
|
13402
13680
|
}
|
|
13403
13681
|
async function writeClaudeLocalSettings(cwd, b) {
|
|
13404
|
-
const file =
|
|
13405
|
-
|
|
13682
|
+
const file = join5(cwd, SETTINGS_REL);
|
|
13683
|
+
mkdirSync4(join5(cwd, ".claude"), { recursive: true });
|
|
13406
13684
|
let existing = {};
|
|
13407
13685
|
if (existsSync5(file)) {
|
|
13408
13686
|
try {
|
|
@@ -13424,7 +13702,7 @@ async function writeClaudeLocalSettings(cwd, b) {
|
|
|
13424
13702
|
writeFileSync2(tmp, `${JSON.stringify(next, null, 2)}
|
|
13425
13703
|
`);
|
|
13426
13704
|
renameSync(tmp, file);
|
|
13427
|
-
if (existsSync5(
|
|
13705
|
+
if (existsSync5(join5(cwd, ".git")))
|
|
13428
13706
|
await excludeFromGit(cwd, SETTINGS_REL);
|
|
13429
13707
|
}
|
|
13430
13708
|
|
|
@@ -13549,12 +13827,12 @@ class AgentRegistry {
|
|
|
13549
13827
|
const name = this.packageName(spec);
|
|
13550
13828
|
if (!name || spec.run.kind === "command")
|
|
13551
13829
|
return null;
|
|
13552
|
-
const pkgDir =
|
|
13830
|
+
const pkgDir = join6(this.dir, spec.id, "node_modules", name);
|
|
13553
13831
|
if (spec.run.kind === "npm-native") {
|
|
13554
|
-
const bin =
|
|
13832
|
+
const bin = join6(pkgDir, spec.run.bin);
|
|
13555
13833
|
return existsSync6(bin) ? bin : null;
|
|
13556
13834
|
}
|
|
13557
|
-
const pkgJson =
|
|
13835
|
+
const pkgJson = join6(pkgDir, "package.json");
|
|
13558
13836
|
if (!existsSync6(pkgJson))
|
|
13559
13837
|
return null;
|
|
13560
13838
|
try {
|
|
@@ -13562,7 +13840,7 @@ class AgentRegistry {
|
|
|
13562
13840
|
const rel = typeof meta.bin === "string" ? meta.bin : meta.bin?.[spec.run.bin];
|
|
13563
13841
|
if (!rel)
|
|
13564
13842
|
return null;
|
|
13565
|
-
const script =
|
|
13843
|
+
const script = join6(dirname3(pkgJson), rel);
|
|
13566
13844
|
return existsSync6(script) ? script : null;
|
|
13567
13845
|
} catch (e) {
|
|
13568
13846
|
log.warn("agents", `cannot read ${pkgJson}`, e);
|
|
@@ -13616,10 +13894,10 @@ class AgentRegistry {
|
|
|
13616
13894
|
if (this.installed(spec) && this.installedVersion(spec) === spec.run.version)
|
|
13617
13895
|
return Promise.resolve();
|
|
13618
13896
|
const { version } = spec.run;
|
|
13619
|
-
const dir =
|
|
13897
|
+
const dir = join6(this.dir, id);
|
|
13620
13898
|
const p = (async () => {
|
|
13621
|
-
|
|
13622
|
-
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)}
|
|
13623
13901
|
`);
|
|
13624
13902
|
log.info("agents", `installing ${pkg}@${version} for ${id}`);
|
|
13625
13903
|
const r = await this.installer(dir, pkg, version);
|
|
@@ -13643,7 +13921,7 @@ class AgentRegistry {
|
|
|
13643
13921
|
const name = this.packageName(spec);
|
|
13644
13922
|
if (!name)
|
|
13645
13923
|
return null;
|
|
13646
|
-
const pkgJson =
|
|
13924
|
+
const pkgJson = join6(this.dir, spec.id, "node_modules", name, "package.json");
|
|
13647
13925
|
try {
|
|
13648
13926
|
return JSON.parse(readFileSync6(pkgJson, "utf8")).version ?? null;
|
|
13649
13927
|
} catch {
|
|
@@ -13906,6 +14184,9 @@ function previewContext(p) {
|
|
|
13906
14184
|
case "setup":
|
|
13907
14185
|
line = "Toyon is setting this worktree up and starts the preview when that is done; the next message will say where it answers.";
|
|
13908
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;
|
|
13909
14190
|
case "starting":
|
|
13910
14191
|
line = `Toyon is starting the preview; it answers${at} once it is up.`;
|
|
13911
14192
|
break;
|
|
@@ -13920,8 +14201,12 @@ function previewContext(p) {
|
|
|
13920
14201
|
}
|
|
13921
14202
|
return line;
|
|
13922
14203
|
}
|
|
13923
|
-
function imageCaption(ref) {
|
|
13924
|
-
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.`;
|
|
13925
14210
|
}
|
|
13926
14211
|
function pasteCaption(ref) {
|
|
13927
14212
|
const label = attachmentLabel("paste", ref.n);
|
|
@@ -13967,7 +14252,7 @@ function attachmentBlocks(a) {
|
|
|
13967
14252
|
switch (a.kind) {
|
|
13968
14253
|
case "image":
|
|
13969
14254
|
return [
|
|
13970
|
-
textBlock(imageCaption(a.ref)),
|
|
14255
|
+
textBlock(imageCaption(a.ref, a.path)),
|
|
13971
14256
|
{ type: "image", mimeType: a.ref.mimeType, data: a.bytes.toString("base64") }
|
|
13972
14257
|
];
|
|
13973
14258
|
case "paste":
|
|
@@ -13975,6 +14260,13 @@ function attachmentBlocks(a) {
|
|
|
13975
14260
|
<pasted-text ${a.ref.n}>
|
|
13976
14261
|
${a.text}
|
|
13977
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
|
+
];
|
|
13978
14270
|
case "pick":
|
|
13979
14271
|
return [textBlock(pickCaption(a.ref))];
|
|
13980
14272
|
}
|
|
@@ -13983,9 +14275,9 @@ ${a.text}
|
|
|
13983
14275
|
// ../daemon/src/agent/transcript.ts
|
|
13984
14276
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
13985
14277
|
import { appendFile } from "fs/promises";
|
|
13986
|
-
import { join as
|
|
14278
|
+
import { join as join7 } from "path";
|
|
13987
14279
|
function transcriptPathFor(transcriptsDir, worktreeId) {
|
|
13988
|
-
return
|
|
14280
|
+
return join7(transcriptsDir, `${worktreeId}.jsonl`);
|
|
13989
14281
|
}
|
|
13990
14282
|
function coalesce(entries) {
|
|
13991
14283
|
const out = [];
|
|
@@ -14140,7 +14432,7 @@ async function cleanUp(ctx, a, sessionId) {
|
|
|
14140
14432
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
14141
14433
|
import { open as open2 } from "fs/promises";
|
|
14142
14434
|
import { homedir as homedir4 } from "os";
|
|
14143
|
-
import { basename as basename2, dirname as dirname4, join as
|
|
14435
|
+
import { basename as basename2, dirname as dirname4, join as join8 } from "path";
|
|
14144
14436
|
var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
|
|
14145
14437
|
function backgroundStart(input, output) {
|
|
14146
14438
|
const flag = input && typeof input === "object" ? input.run_in_background : undefined;
|
|
@@ -14153,14 +14445,14 @@ function spawnDetached(output) {
|
|
|
14153
14445
|
return output.includes("Async agent launched successfully");
|
|
14154
14446
|
}
|
|
14155
14447
|
function claudeConfigDir() {
|
|
14156
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
14448
|
+
return process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude");
|
|
14157
14449
|
}
|
|
14158
14450
|
function sessionLogFor(file, configDir = claudeConfigDir()) {
|
|
14159
14451
|
const tasks = dirname4(file);
|
|
14160
14452
|
if (basename2(tasks) !== "tasks")
|
|
14161
14453
|
return null;
|
|
14162
14454
|
const session = dirname4(tasks);
|
|
14163
|
-
const path =
|
|
14455
|
+
const path = join8(configDir, "projects", basename2(dirname4(session)), `${basename2(session)}.jsonl`);
|
|
14164
14456
|
return existsSync8(path) ? path : null;
|
|
14165
14457
|
}
|
|
14166
14458
|
function taskEnd(text, toolId) {
|
|
@@ -14521,11 +14813,26 @@ function endOfAsk(req, res, memos) {
|
|
|
14521
14813
|
isError: !allowed
|
|
14522
14814
|
};
|
|
14523
14815
|
}
|
|
14524
|
-
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) {
|
|
14525
14826
|
const out = [];
|
|
14526
14827
|
for (const [toolId, memo] of memos) {
|
|
14527
14828
|
if (memo.ended || !memo.writing || memo.parent !== parent)
|
|
14528
14829
|
continue;
|
|
14830
|
+
if (opening && memo.held) {
|
|
14831
|
+
const sent = release(toolId, memo);
|
|
14832
|
+
if (sent)
|
|
14833
|
+
out.push(sent);
|
|
14834
|
+
continue;
|
|
14835
|
+
}
|
|
14529
14836
|
memo.ended = true;
|
|
14530
14837
|
out.push({ type: "tool-end", toolId });
|
|
14531
14838
|
}
|
|
@@ -14554,7 +14861,7 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14554
14861
|
}
|
|
14555
14862
|
case "tool_call": {
|
|
14556
14863
|
const spawn = spawnOf(update._meta);
|
|
14557
|
-
const out = abandoned(memos, spawn.parentToolId);
|
|
14864
|
+
const out = abandoned(memos, spawn.parentToolId, true);
|
|
14558
14865
|
const input = update.rawInput ?? { locations: update.locations ?? [] };
|
|
14559
14866
|
const head = heading(update);
|
|
14560
14867
|
const memo = {
|
|
@@ -14609,26 +14916,27 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14609
14916
|
...spawn
|
|
14610
14917
|
});
|
|
14611
14918
|
}
|
|
14612
|
-
|
|
14613
|
-
return out;
|
|
14614
|
-
const refined = { type: "tool-update", toolId: update.toolCallId };
|
|
14919
|
+
const refined = { ...memo.held };
|
|
14615
14920
|
const title = memo.name === TOOL_SEARCH ? toolSearchQuery(update.rawInput) : update.title;
|
|
14616
|
-
if (title && title !== memo.title)
|
|
14617
|
-
memo.title = title;
|
|
14618
|
-
|
|
14619
|
-
}
|
|
14620
|
-
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)
|
|
14621
14924
|
refined.name = memo.name = update.name;
|
|
14622
|
-
}
|
|
14623
14925
|
if (update.kind && update.kind !== memo.kind)
|
|
14624
14926
|
refined.kind = memo.kind = update.kind;
|
|
14625
|
-
if (update.rawInput !== undefined)
|
|
14927
|
+
if (update.rawInput !== undefined)
|
|
14626
14928
|
refined.input = memo.input = update.rawInput;
|
|
14627
|
-
|
|
14628
|
-
|
|
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 });
|
|
14629
14939
|
}
|
|
14630
|
-
if (Object.keys(refined).length > 2 && !memo.ended && out.length === 0)
|
|
14631
|
-
out.push(refined);
|
|
14632
14940
|
if (update.content)
|
|
14633
14941
|
memo.content = [...memo.content, ...update.content];
|
|
14634
14942
|
if (update.rawOutput !== undefined)
|
|
@@ -14776,6 +15084,11 @@ function steerOutcome(value) {
|
|
|
14776
15084
|
// ../daemon/src/agent/acp/session.ts
|
|
14777
15085
|
var DEFAULT_IDLE_MS = Number(process.env.TOYON_AGENT_IDLE_MS) || 5 * 60000;
|
|
14778
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
|
+
});
|
|
14779
15092
|
function recorded(reply) {
|
|
14780
15093
|
if (reply?.kind === "choice")
|
|
14781
15094
|
return { choiceId: reply.choiceId };
|
|
@@ -14891,7 +15204,7 @@ class AcpSession {
|
|
|
14891
15204
|
return this.queue.length > 0 || this.steered.length > 0 || this.refused !== null || this.asks.size > 0;
|
|
14892
15205
|
}
|
|
14893
15206
|
get queueItems() {
|
|
14894
|
-
return this.waiting().map(
|
|
15207
|
+
return this.waiting().map(queued);
|
|
14895
15208
|
}
|
|
14896
15209
|
waiting() {
|
|
14897
15210
|
return this.queue.filter((q) => !q.recorded);
|
|
@@ -14900,13 +15213,19 @@ class AcpSession {
|
|
|
14900
15213
|
queueChanged() {
|
|
14901
15214
|
this.onQueueChange?.();
|
|
14902
15215
|
}
|
|
14903
|
-
unqueue(index) {
|
|
15216
|
+
unqueue(index, taken) {
|
|
14904
15217
|
const item = this.waiting()[index];
|
|
14905
15218
|
if (!item)
|
|
14906
15219
|
return;
|
|
14907
15220
|
this.queue.splice(this.queue.indexOf(item), 1);
|
|
15221
|
+
taken?.(queued(item));
|
|
15222
|
+
this.letGo(item);
|
|
14908
15223
|
this.queueChanged();
|
|
14909
15224
|
}
|
|
15225
|
+
letGo(item) {
|
|
15226
|
+
if (!item.recorded)
|
|
15227
|
+
this.d.attachments.uploads.release(uploadIds(item.attachments));
|
|
15228
|
+
}
|
|
14910
15229
|
transcript() {
|
|
14911
15230
|
return this.log.entries;
|
|
14912
15231
|
}
|
|
@@ -14924,8 +15243,14 @@ class AcpSession {
|
|
|
14924
15243
|
send(text, opts = {}) {
|
|
14925
15244
|
if (this.stopped)
|
|
14926
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 = {}) {
|
|
14927
15251
|
const { context, attachments } = opts;
|
|
14928
15252
|
const item = { text, context, ...attachments?.length ? { attachments } : {} };
|
|
15253
|
+
this.d.attachments.uploads.hold(uploadIds(attachments));
|
|
14929
15254
|
if ((this.running || this.own) && !this.interrupted && !this.held && this.live?.conn.steering) {
|
|
14930
15255
|
return fireAndForget(this.d.worktreeId, this.steer(item), "agent steer");
|
|
14931
15256
|
}
|
|
@@ -15012,6 +15337,8 @@ class AcpSession {
|
|
|
15012
15337
|
async close() {
|
|
15013
15338
|
this.stopped = true;
|
|
15014
15339
|
this.clearReaper();
|
|
15340
|
+
for (const item of this.queue)
|
|
15341
|
+
this.letGo(item);
|
|
15015
15342
|
this.queue = [];
|
|
15016
15343
|
this.steered = [];
|
|
15017
15344
|
this.queueChanged();
|
|
@@ -15135,8 +15462,8 @@ class AcpSession {
|
|
|
15135
15462
|
retry() {
|
|
15136
15463
|
const item = this.refused;
|
|
15137
15464
|
this.refused = null;
|
|
15138
|
-
if (item)
|
|
15139
|
-
this.
|
|
15465
|
+
if (item && !this.stopped)
|
|
15466
|
+
this.enqueue(item);
|
|
15140
15467
|
}
|
|
15141
15468
|
planWithheld(conn, methodId) {
|
|
15142
15469
|
return this.d.planSignIn?.() === false && conn.spec.terminalLogins?.[methodId]?.plan === true;
|
|
@@ -15159,8 +15486,12 @@ class AcpSession {
|
|
|
15159
15486
|
}
|
|
15160
15487
|
async record({ text, attachments }) {
|
|
15161
15488
|
const stored = [];
|
|
15162
|
-
|
|
15163
|
-
|
|
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
|
+
}
|
|
15164
15495
|
this.emit({
|
|
15165
15496
|
type: "user-message",
|
|
15166
15497
|
text,
|
|
@@ -15182,6 +15513,11 @@ class AcpSession {
|
|
|
15182
15513
|
}
|
|
15183
15514
|
async runTurn(item) {
|
|
15184
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
|
+
}
|
|
15185
15521
|
this.emit({ type: "turn-start", ts: Date.now() });
|
|
15186
15522
|
const live = await this.ensureLive("a turn");
|
|
15187
15523
|
await this.applyMode(live);
|
|
@@ -15410,6 +15746,10 @@ class AcpSession {
|
|
|
15410
15746
|
if (params.update.sessionUpdate === "config_option_update")
|
|
15411
15747
|
this.absorb(live, params.update.configOptions);
|
|
15412
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) {
|
|
15413
15753
|
if (!this.running)
|
|
15414
15754
|
this.trackOwn(events);
|
|
15415
15755
|
for (const ev of events) {
|
|
@@ -15418,6 +15758,23 @@ class AcpSession {
|
|
|
15418
15758
|
this.emit(ev.type === "session-info" ? { ...ev, sessionId: live.sessionId } : ev);
|
|
15419
15759
|
}
|
|
15420
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
|
+
}
|
|
15421
15778
|
onPermission(params, bounds, sandboxed) {
|
|
15422
15779
|
const tools = this.live?.tools;
|
|
15423
15780
|
return Promise.resolve(this.answerPermission(params, bounds, sandboxed)).then((res) => {
|
|
@@ -15476,7 +15833,7 @@ class AcpSession {
|
|
|
15476
15833
|
async sayPlanEdited(proposed, path) {
|
|
15477
15834
|
if (!await this.d.planEdited?.(path, proposed))
|
|
15478
15835
|
return;
|
|
15479
|
-
this.
|
|
15836
|
+
this.post(`I edited the plan before approving it. Build what is in ${path}, not the plan you proposed.`);
|
|
15480
15837
|
}
|
|
15481
15838
|
onElicit(params, signal) {
|
|
15482
15839
|
const live = this.live;
|
|
@@ -15671,9 +16028,9 @@ function sameCommands(a, b) {
|
|
|
15671
16028
|
}
|
|
15672
16029
|
|
|
15673
16030
|
// ../daemon/src/agent/planDoc.ts
|
|
15674
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
15675
|
-
import { join as
|
|
15676
|
-
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");
|
|
15677
16034
|
var PLAN_NAME = /^(\d+)\.md$/;
|
|
15678
16035
|
function isPlanPath(rel) {
|
|
15679
16036
|
return rel.startsWith(`${PLANS_DIR}/`) && PLAN_NAME.test(rel.slice(PLANS_DIR.length + 1));
|
|
@@ -15682,7 +16039,7 @@ function nextNumber(dir) {
|
|
|
15682
16039
|
if (!existsSync9(dir))
|
|
15683
16040
|
return 1;
|
|
15684
16041
|
let max = 0;
|
|
15685
|
-
for (const name of
|
|
16042
|
+
for (const name of readdirSync3(dir)) {
|
|
15686
16043
|
const n = Number(PLAN_NAME.exec(name)?.[1]);
|
|
15687
16044
|
if (n > max)
|
|
15688
16045
|
max = n;
|
|
@@ -15690,11 +16047,11 @@ function nextNumber(dir) {
|
|
|
15690
16047
|
return max + 1;
|
|
15691
16048
|
}
|
|
15692
16049
|
async function writePlanDoc(cwd, markdown) {
|
|
15693
|
-
const dir =
|
|
15694
|
-
const rel =
|
|
16050
|
+
const dir = join9(cwd, PLANS_DIR);
|
|
16051
|
+
const rel = join9(PLANS_DIR, `${nextNumber(dir)}.md`);
|
|
15695
16052
|
try {
|
|
15696
|
-
|
|
15697
|
-
writeFileSync4(
|
|
16053
|
+
mkdirSync6(dir, { recursive: true });
|
|
16054
|
+
writeFileSync4(join9(cwd, rel), markdown.endsWith(`
|
|
15698
16055
|
`) ? markdown : `${markdown}
|
|
15699
16056
|
`);
|
|
15700
16057
|
} catch (e) {
|
|
@@ -15705,7 +16062,7 @@ async function writePlanDoc(cwd, markdown) {
|
|
|
15705
16062
|
return rel;
|
|
15706
16063
|
}
|
|
15707
16064
|
function planEdited(cwd, rel, proposed) {
|
|
15708
|
-
const file =
|
|
16065
|
+
const file = join9(cwd, rel);
|
|
15709
16066
|
if (!existsSync9(file))
|
|
15710
16067
|
return false;
|
|
15711
16068
|
try {
|
|
@@ -17010,11 +17367,12 @@ function defaultAgent(wt, d, preview, onProcess) {
|
|
|
17010
17367
|
launch: (spec) => d.agents.command(spec),
|
|
17011
17368
|
planSignIn: () => d.managed?.planSignIn ?? true,
|
|
17012
17369
|
transcriptsDir: d.paths.transcriptsDir,
|
|
17013
|
-
attachments: d.attachments ?? new AttachmentStore(d.paths.attachmentsDir),
|
|
17370
|
+
attachments: d.attachments ?? new AttachmentStore(d.paths.attachmentsDir, new UploadStore(d.paths.uploadsDir)),
|
|
17014
17371
|
getSessionId: () => d.state.session(wt.id),
|
|
17015
17372
|
setSessionId: (id) => d.state.setSession(wt.id, id),
|
|
17016
17373
|
onEvent: (event, seq) => d.hub.emit("agent", wt.id, seq, event),
|
|
17017
17374
|
onStatus: (status) => d.hub.emit("agentStatus", wt.id, status),
|
|
17375
|
+
beforeTurn: async () => d.turnStarting?.(wt.id),
|
|
17018
17376
|
onAuth: (agentId, o) => d.accounts?.observe(agentId, o),
|
|
17019
17377
|
seedCommands: () => d.state.cachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId),
|
|
17020
17378
|
onCommandsLearned: (commands) => d.state.setCachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId, commands),
|
|
@@ -17318,9 +17676,12 @@ class RuntimeRegistry {
|
|
|
17318
17676
|
return rt;
|
|
17319
17677
|
}
|
|
17320
17678
|
async start(wt, repo) {
|
|
17321
|
-
|
|
17679
|
+
const record = this.deps.state.worktree(wt.id);
|
|
17680
|
+
if (!record)
|
|
17322
17681
|
return;
|
|
17323
17682
|
const rt = this.ensureAgent(wt);
|
|
17683
|
+
if (record.parked)
|
|
17684
|
+
return;
|
|
17324
17685
|
const run = repo.needsSetup ? EMPTY_RUN : resolveRun(repo, wt);
|
|
17325
17686
|
const leads = wt.kind !== "main" || !this.deps.mainLeads || this.deps.mainLeads(wt.repoId);
|
|
17326
17687
|
if (rt.procs) {
|
|
@@ -17719,6 +18080,11 @@ class RuntimeRegistry {
|
|
|
17719
18080
|
return rt?.procs ? previewTargetOf(rt.procs, rt.previewName) : null;
|
|
17720
18081
|
}
|
|
17721
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
|
+
}
|
|
17722
18088
|
if (this.settingUp.has(id))
|
|
17723
18089
|
return { status: "setup" };
|
|
17724
18090
|
const rt = this.runtimes.get(id);
|
|
@@ -17783,17 +18149,17 @@ class RuntimeRegistry {
|
|
|
17783
18149
|
}
|
|
17784
18150
|
|
|
17785
18151
|
// ../daemon/src/worktrees/naming.ts
|
|
17786
|
-
import { randomBytes } from "crypto";
|
|
18152
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
17787
18153
|
import { existsSync as existsSync11 } from "fs";
|
|
17788
|
-
import { join as
|
|
18154
|
+
import { join as join10 } from "path";
|
|
17789
18155
|
function shortId() {
|
|
17790
|
-
return
|
|
18156
|
+
return randomBytes2(5).toString("hex");
|
|
17791
18157
|
}
|
|
17792
18158
|
function freeSlot(parent) {
|
|
17793
18159
|
for (;; ) {
|
|
17794
18160
|
const id = shortId();
|
|
17795
18161
|
const dir = `wt-${id.slice(0, 4)}`;
|
|
17796
|
-
if (!existsSync11(
|
|
18162
|
+
if (!existsSync11(join10(parent, dir)))
|
|
17797
18163
|
return { id, dir };
|
|
17798
18164
|
}
|
|
17799
18165
|
}
|
|
@@ -18133,6 +18499,8 @@ function attachmentText(a) {
|
|
|
18133
18499
|
return a.text;
|
|
18134
18500
|
if (a.kind === "image")
|
|
18135
18501
|
return a.name ? `image ${a.name}` : "an image";
|
|
18502
|
+
if (a.kind === "file")
|
|
18503
|
+
return `file ${a.name}`;
|
|
18136
18504
|
return `element <${a.component ?? a.tag}>${a.text ? ` "${a.text}"` : ""}`;
|
|
18137
18505
|
}
|
|
18138
18506
|
function parseName(text) {
|
|
@@ -18207,29 +18575,29 @@ function makePlanner(agents) {
|
|
|
18207
18575
|
}
|
|
18208
18576
|
|
|
18209
18577
|
// ../daemon/src/core/assets.ts
|
|
18210
|
-
import { existsSync as existsSync12, readdirSync as
|
|
18211
|
-
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";
|
|
18212
18580
|
function locateAssets(here, env = process.env) {
|
|
18213
|
-
const packaged = existsSync12(
|
|
18581
|
+
const packaged = existsSync12(join11(here, "shell", "index.html"));
|
|
18214
18582
|
return {
|
|
18215
|
-
shellDist: env.TOYON_SHELL_DIST ?? (packaged ?
|
|
18216
|
-
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")),
|
|
18217
18585
|
sourceRoot: packaged ? null : resolve5(here, "../../.."),
|
|
18218
|
-
packageJson: packaged ?
|
|
18586
|
+
packageJson: packaged ? join11(here, "..", "package.json") : null
|
|
18219
18587
|
};
|
|
18220
18588
|
}
|
|
18221
18589
|
var KEEP_MS = 3 * 24 * 60 * 60 * 1000;
|
|
18222
18590
|
function pruneAssets(shellDist) {
|
|
18223
|
-
const dir =
|
|
18591
|
+
const dir = join11(shellDist, "assets");
|
|
18224
18592
|
let names;
|
|
18225
18593
|
try {
|
|
18226
|
-
names =
|
|
18594
|
+
names = readdirSync4(dir);
|
|
18227
18595
|
} catch {
|
|
18228
18596
|
return 0;
|
|
18229
18597
|
}
|
|
18230
18598
|
const found = [];
|
|
18231
18599
|
for (const name of names) {
|
|
18232
|
-
const path =
|
|
18600
|
+
const path = join11(dir, name);
|
|
18233
18601
|
try {
|
|
18234
18602
|
found.push({ path, mtime: statSync4(path).mtimeMs });
|
|
18235
18603
|
} catch {}
|
|
@@ -18242,7 +18610,7 @@ function pruneAssets(shellDist) {
|
|
|
18242
18610
|
if (newest - f.mtime <= KEEP_MS)
|
|
18243
18611
|
continue;
|
|
18244
18612
|
try {
|
|
18245
|
-
|
|
18613
|
+
rmSync2(f.path, { recursive: true, force: true });
|
|
18246
18614
|
gone++;
|
|
18247
18615
|
} catch {}
|
|
18248
18616
|
}
|
|
@@ -18894,7 +19262,7 @@ class SelfWatch {
|
|
|
18894
19262
|
}
|
|
18895
19263
|
|
|
18896
19264
|
// ../daemon/src/core/state.ts
|
|
18897
|
-
import { randomBytes as
|
|
19265
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
18898
19266
|
import { existsSync as existsSync14, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
18899
19267
|
var GROUPS_SAVE_MS = 250;
|
|
18900
19268
|
var SEEN_WORKTREES = 50;
|
|
@@ -18956,7 +19324,7 @@ function loadOrCreateToken(paths) {
|
|
|
18956
19324
|
const TOKEN_FILE = paths.tokenFile;
|
|
18957
19325
|
if (existsSync14(TOKEN_FILE))
|
|
18958
19326
|
return readFileSync10(TOKEN_FILE, "utf8").trim();
|
|
18959
|
-
const token =
|
|
19327
|
+
const token = randomBytes3(32).toString("hex");
|
|
18960
19328
|
writeFileSync5(TOKEN_FILE, token, { mode: 384 });
|
|
18961
19329
|
return token;
|
|
18962
19330
|
}
|
|
@@ -19170,7 +19538,7 @@ class StateStore {
|
|
|
19170
19538
|
}
|
|
19171
19539
|
|
|
19172
19540
|
// ../daemon/src/design/service.ts
|
|
19173
|
-
import { join as
|
|
19541
|
+
import { join as join12 } from "path";
|
|
19174
19542
|
|
|
19175
19543
|
// ../daemon/src/design/scan.ts
|
|
19176
19544
|
var COLOR = /^(#|rgba?\(|hsla?\(|oklch\(|oklab\(|lab\(|lch\(|color\(|color-mix\(|light-dark\()/i;
|
|
@@ -19620,7 +19988,7 @@ class DesignService {
|
|
|
19620
19988
|
async function readAll(root, paths) {
|
|
19621
19989
|
const out = [];
|
|
19622
19990
|
for (const path of paths) {
|
|
19623
|
-
const file = Bun.file(
|
|
19991
|
+
const file = Bun.file(join12(root, path));
|
|
19624
19992
|
if (file.size === 0 || file.size > MAX_FILE_BYTES)
|
|
19625
19993
|
continue;
|
|
19626
19994
|
try {
|
|
@@ -19735,7 +20103,7 @@ function typicalUse(classes) {
|
|
|
19735
20103
|
var normal = (s) => s.replace(/[-_]/g, "").toLowerCase();
|
|
19736
20104
|
async function loadProjectTypescript(root) {
|
|
19737
20105
|
try {
|
|
19738
|
-
return await import(
|
|
20106
|
+
return await import(join12(root, "node_modules", "typescript", "lib", "typescript.js"));
|
|
19739
20107
|
} catch {
|
|
19740
20108
|
return null;
|
|
19741
20109
|
}
|
|
@@ -19748,28 +20116,32 @@ var SAVE_DELAY_MS = 1000;
|
|
|
19748
20116
|
class DraftStore {
|
|
19749
20117
|
d;
|
|
19750
20118
|
drafts = {};
|
|
20119
|
+
lists = {};
|
|
20120
|
+
dirty = { text: false, lists: false };
|
|
19751
20121
|
saveTimer = null;
|
|
19752
20122
|
constructor(d) {
|
|
19753
20123
|
this.d = d;
|
|
19754
|
-
|
|
19755
|
-
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
|
|
19761
|
-
|
|
19762
|
-
|
|
19763
|
-
|
|
19764
|
-
|
|
19765
|
-
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;
|
|
19766
20135
|
}
|
|
19767
20136
|
}
|
|
19768
20137
|
all() {
|
|
19769
20138
|
return { ...this.drafts };
|
|
19770
20139
|
}
|
|
20140
|
+
allAttachments() {
|
|
20141
|
+
return { ...this.lists };
|
|
20142
|
+
}
|
|
19771
20143
|
has(boxId) {
|
|
19772
|
-
return !!this.drafts[boxId]?.trim();
|
|
20144
|
+
return !!this.drafts[boxId]?.trim() || !!this.lists[boxId]?.length;
|
|
19773
20145
|
}
|
|
19774
20146
|
text(boxId) {
|
|
19775
20147
|
return this.drafts[boxId] ?? "";
|
|
@@ -19781,22 +20153,67 @@ class DraftStore {
|
|
|
19781
20153
|
this.drafts[boxId] = text;
|
|
19782
20154
|
else
|
|
19783
20155
|
delete this.drafts[boxId];
|
|
19784
|
-
this.scheduleSave();
|
|
20156
|
+
this.scheduleSave("text");
|
|
19785
20157
|
this.d.hub.emit("draftChanged", boxId, text, clientId);
|
|
19786
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
|
+
}
|
|
19787
20188
|
drop(boxId) {
|
|
19788
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;
|
|
19789
20203
|
}
|
|
19790
20204
|
prune(known) {
|
|
19791
|
-
let changed = false;
|
|
19792
20205
|
for (const id of Object.keys(this.drafts)) {
|
|
19793
20206
|
if (known(id))
|
|
19794
20207
|
continue;
|
|
19795
20208
|
delete this.drafts[id];
|
|
19796
|
-
|
|
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");
|
|
19797
20216
|
}
|
|
19798
|
-
if (changed)
|
|
19799
|
-
this.scheduleSave();
|
|
19800
20217
|
}
|
|
19801
20218
|
flush() {
|
|
19802
20219
|
if (!this.saveTimer)
|
|
@@ -19805,7 +20222,8 @@ class DraftStore {
|
|
|
19805
20222
|
this.saveTimer = null;
|
|
19806
20223
|
this.write();
|
|
19807
20224
|
}
|
|
19808
|
-
scheduleSave() {
|
|
20225
|
+
scheduleSave(which) {
|
|
20226
|
+
this.dirty[which] = true;
|
|
19809
20227
|
if (this.saveTimer)
|
|
19810
20228
|
return;
|
|
19811
20229
|
this.saveTimer = setTimeout(() => {
|
|
@@ -19815,13 +20233,31 @@ class DraftStore {
|
|
|
19815
20233
|
this.saveTimer.unref?.();
|
|
19816
20234
|
}
|
|
19817
20235
|
write() {
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
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);
|
|
19825
20261
|
}
|
|
19826
20262
|
}
|
|
19827
20263
|
|
|
@@ -20050,17 +20486,17 @@ class ExecService {
|
|
|
20050
20486
|
}
|
|
20051
20487
|
|
|
20052
20488
|
// ../daemon/src/files/open.ts
|
|
20053
|
-
import { stat as
|
|
20489
|
+
import { stat as stat3 } from "fs/promises";
|
|
20054
20490
|
import { basename as basename3, resolve as resolve7 } from "path";
|
|
20055
20491
|
|
|
20056
20492
|
// ../daemon/src/repos/browse.ts
|
|
20057
20493
|
import { existsSync as existsSync16, statSync as statSync5 } from "fs";
|
|
20058
20494
|
import { readdir } from "fs/promises";
|
|
20059
20495
|
import { homedir as homedir7 } from "os";
|
|
20060
|
-
import { dirname as dirname5, join as
|
|
20496
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
20061
20497
|
var MAX_ENTRIES = 40;
|
|
20062
20498
|
function expandTilde(raw) {
|
|
20063
|
-
return raw === "~" || raw.startsWith("~/") ?
|
|
20499
|
+
return raw === "~" || raw.startsWith("~/") ? join13(homedir7(), raw.slice(1)) : raw;
|
|
20064
20500
|
}
|
|
20065
20501
|
function collapseTilde(abs) {
|
|
20066
20502
|
const home = homedir7();
|
|
@@ -20071,7 +20507,7 @@ function holdsNothing(names) {
|
|
|
20071
20507
|
}
|
|
20072
20508
|
async function describeFolder(abs) {
|
|
20073
20509
|
const path = collapseTilde(abs);
|
|
20074
|
-
if (existsSync16(
|
|
20510
|
+
if (existsSync16(join13(abs, ".git")))
|
|
20075
20511
|
return { path, kind: "project" };
|
|
20076
20512
|
try {
|
|
20077
20513
|
return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
|
|
@@ -20088,7 +20524,7 @@ function describe2(typed, parent) {
|
|
|
20088
20524
|
return {
|
|
20089
20525
|
exists: existsSync16(typed),
|
|
20090
20526
|
isDir,
|
|
20091
|
-
isRepo: isDir && existsSync16(
|
|
20527
|
+
isRepo: isDir && existsSync16(join13(typed, ".git")),
|
|
20092
20528
|
parentExists: existsSync16(parent)
|
|
20093
20529
|
};
|
|
20094
20530
|
}
|
|
@@ -20113,8 +20549,8 @@ async function browsePath(raw) {
|
|
|
20113
20549
|
continue;
|
|
20114
20550
|
if (!name.toLowerCase().startsWith(prefix))
|
|
20115
20551
|
continue;
|
|
20116
|
-
const abs =
|
|
20117
|
-
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")) });
|
|
20118
20554
|
}
|
|
20119
20555
|
entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
|
|
20120
20556
|
return { entries: entries.slice(0, MAX_ENTRIES), target };
|
|
@@ -20151,7 +20587,7 @@ function looksBinary(text) {
|
|
|
20151
20587
|
|
|
20152
20588
|
// ../daemon/src/files/service.ts
|
|
20153
20589
|
import { spawn as spawn4 } from "child_process";
|
|
20154
|
-
import { stat, unlink } from "fs/promises";
|
|
20590
|
+
import { stat as stat2, unlink } from "fs/promises";
|
|
20155
20591
|
|
|
20156
20592
|
// ../daemon/src/git/blame.ts
|
|
20157
20593
|
var NONE = { commits: [], lines: [] };
|
|
@@ -20222,10 +20658,13 @@ async function withLock(key, fn) {
|
|
|
20222
20658
|
function withRepoLock(repoPath, fn) {
|
|
20223
20659
|
return withLock(repoPath, fn);
|
|
20224
20660
|
}
|
|
20661
|
+
function withFetchLock(repoPath, fn) {
|
|
20662
|
+
return withLock(`fetch:${repoPath}`, fn);
|
|
20663
|
+
}
|
|
20225
20664
|
var fileLockKey = (absPath) => `file:${absPath}`;
|
|
20226
20665
|
|
|
20227
20666
|
// ../daemon/src/git/status.ts
|
|
20228
|
-
import { join as
|
|
20667
|
+
import { join as join14 } from "path";
|
|
20229
20668
|
async function treeFingerprint(worktreePath) {
|
|
20230
20669
|
const [head, diff, untracked] = await Promise.all([
|
|
20231
20670
|
git(worktreePath, "rev-parse", "HEAD"),
|
|
@@ -20235,7 +20674,7 @@ async function treeFingerprint(worktreePath) {
|
|
|
20235
20674
|
const others = await Promise.all(untracked.out.split(`
|
|
20236
20675
|
`).filter(Boolean).map(async (p) => {
|
|
20237
20676
|
try {
|
|
20238
|
-
const f = Bun.file(
|
|
20677
|
+
const f = Bun.file(join14(worktreePath, p));
|
|
20239
20678
|
const body = f.size > 2000000 ? String(f.size) : Bun.hash(await f.arrayBuffer()).toString(36);
|
|
20240
20679
|
return `${p}:${body}`;
|
|
20241
20680
|
} catch {
|
|
@@ -20268,6 +20707,10 @@ async function fileBefore(worktreePath, defaultBr, file) {
|
|
|
20268
20707
|
const r = await gitRaw(worktreePath, "show", `${ref}:${file}`);
|
|
20269
20708
|
return r.ok ? r.out : "";
|
|
20270
20709
|
}
|
|
20710
|
+
async function fileAt(worktreePath, commit, file) {
|
|
20711
|
+
const r = await gitRaw(worktreePath, "show", `${commit}:${file}`);
|
|
20712
|
+
return r.ok ? r.out : "";
|
|
20713
|
+
}
|
|
20271
20714
|
function parseNumstat(out) {
|
|
20272
20715
|
const counts = new Map;
|
|
20273
20716
|
for (const line of out.split(`
|
|
@@ -20286,7 +20729,7 @@ async function numstat(worktreePath, ...range) {
|
|
|
20286
20729
|
}
|
|
20287
20730
|
async function untrackedLines(worktreePath, file) {
|
|
20288
20731
|
try {
|
|
20289
|
-
const f = Bun.file(
|
|
20732
|
+
const f = Bun.file(join14(worktreePath, file));
|
|
20290
20733
|
if (f.size > 2000000)
|
|
20291
20734
|
return {};
|
|
20292
20735
|
const buf = new Uint8Array(await f.arrayBuffer());
|
|
@@ -20459,7 +20902,7 @@ var COMMON = 40;
|
|
|
20459
20902
|
var NEAR = 3;
|
|
20460
20903
|
var CLEAR_LEAD = 1.5;
|
|
20461
20904
|
var ELEMENT_SOURCES_MAX = 8;
|
|
20462
|
-
var
|
|
20905
|
+
var NAME2 = /^[\w-]+$/;
|
|
20463
20906
|
var TAG = /^[a-z][a-z0-9-]*$/;
|
|
20464
20907
|
var NAME_CHAR = /[\w-]/;
|
|
20465
20908
|
function needlesOf(el) {
|
|
@@ -20472,12 +20915,12 @@ function needlesOf(el) {
|
|
|
20472
20915
|
out.set(text, { text, weight, word });
|
|
20473
20916
|
};
|
|
20474
20917
|
if (el.id)
|
|
20475
|
-
add(el.id, 8,
|
|
20918
|
+
add(el.id, 8, NAME2.test(el.id));
|
|
20476
20919
|
const words = (el.text.split(/[&<>"'`${}]/)[0] ?? "").trim().split(/\s+/).slice(0, 6).join(" ");
|
|
20477
20920
|
if (words.length >= 3 && /\p{L}/u.test(words))
|
|
20478
20921
|
add(words, 6, false);
|
|
20479
20922
|
for (const [, value] of el.attrs)
|
|
20480
|
-
add(value, 5,
|
|
20923
|
+
add(value, 5, NAME2.test(value));
|
|
20481
20924
|
for (const c of el.classes)
|
|
20482
20925
|
add(c, 3, true);
|
|
20483
20926
|
return [...out.values()];
|
|
@@ -20643,7 +21086,7 @@ var NOT_WHERE_WRITTEN = [
|
|
|
20643
21086
|
];
|
|
20644
21087
|
var NO_TEXT = { before: "", after: "" };
|
|
20645
21088
|
async function statFile(target) {
|
|
20646
|
-
const st = await
|
|
21089
|
+
const st = await stat2(target).catch((e) => {
|
|
20647
21090
|
if (e.code === "ENOENT")
|
|
20648
21091
|
return null;
|
|
20649
21092
|
throw e;
|
|
@@ -20704,14 +21147,14 @@ class FileService {
|
|
|
20704
21147
|
throw new UserError("unknown worktree");
|
|
20705
21148
|
return r;
|
|
20706
21149
|
}
|
|
20707
|
-
async read(worktreeId, path, ref) {
|
|
21150
|
+
async read(worktreeId, path, ref, since) {
|
|
20708
21151
|
const r = this.require(worktreeId);
|
|
20709
21152
|
const target = resolveInside(r.path, path);
|
|
20710
21153
|
if (ref) {
|
|
20711
21154
|
const { before, after } = await fileAtCommit(r.path, ref, path);
|
|
20712
21155
|
return { ...sides(before, after), version: null, writable: false };
|
|
20713
21156
|
}
|
|
20714
|
-
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);
|
|
20715
21158
|
const st = await statFile(target);
|
|
20716
21159
|
if (st && st.size > FILE_MAX_CHARS) {
|
|
20717
21160
|
return { ...NO_TEXT, version: `${st.size}-${st.mtimeMs}`, writable: false, binary: false, tooLarge: true };
|
|
@@ -20890,7 +21333,7 @@ class OpenService {
|
|
|
20890
21333
|
}
|
|
20891
21334
|
async locate(raw) {
|
|
20892
21335
|
const path = canonical(resolve7(expandTilde(raw)));
|
|
20893
|
-
const st = await
|
|
21336
|
+
const st = await stat3(path).catch((e) => {
|
|
20894
21337
|
if (e.code === "ENOENT")
|
|
20895
21338
|
return null;
|
|
20896
21339
|
throw e;
|
|
@@ -21169,9 +21612,9 @@ class AfterLand {
|
|
|
21169
21612
|
}
|
|
21170
21613
|
|
|
21171
21614
|
// ../daemon/src/repos/registry.ts
|
|
21172
|
-
import { existsSync as existsSync20, mkdirSync as
|
|
21615
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync7, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
21173
21616
|
import { homedir as homedir9 } from "os";
|
|
21174
|
-
import { basename as basename5, dirname as dirname7, join as
|
|
21617
|
+
import { basename as basename5, dirname as dirname7, join as join18 } from "path";
|
|
21175
21618
|
|
|
21176
21619
|
// ../daemon/src/git/base.ts
|
|
21177
21620
|
async function resolveBase(repoPath, defaultBranch, route) {
|
|
@@ -21182,8 +21625,8 @@ async function resolveBase(repoPath, defaultBranch, route) {
|
|
|
21182
21625
|
}
|
|
21183
21626
|
|
|
21184
21627
|
// ../daemon/src/repos/config.ts
|
|
21185
|
-
import { closeSync, existsSync as existsSync17, openSync as openSync2, readdirSync as
|
|
21186
|
-
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";
|
|
21187
21630
|
|
|
21188
21631
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
21189
21632
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -22547,7 +22990,7 @@ function applyEdits(text, edits) {
|
|
|
22547
22990
|
|
|
22548
22991
|
// ../daemon/src/repos/config.ts
|
|
22549
22992
|
var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
|
|
22550
|
-
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)));
|
|
22551
22994
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
22552
22995
|
function parseSettings(text) {
|
|
22553
22996
|
const errors = [];
|
|
@@ -22572,7 +23015,7 @@ function readConfigFile(repoPath) {
|
|
|
22572
23015
|
for (const rel of files) {
|
|
22573
23016
|
let raw;
|
|
22574
23017
|
try {
|
|
22575
|
-
raw = parseSettings(readFileSync12(
|
|
23018
|
+
raw = parseSettings(readFileSync12(join15(repoPath, rel), "utf8"));
|
|
22576
23019
|
} catch (e) {
|
|
22577
23020
|
return { ok: false, reason: `${rel} is not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
22578
23021
|
}
|
|
@@ -22631,7 +23074,7 @@ function configTarget(repoPath, made) {
|
|
|
22631
23074
|
}
|
|
22632
23075
|
function configBody(repoPath, rel, config) {
|
|
22633
23076
|
const place = PLACES.find((p) => p.local === rel);
|
|
22634
|
-
const abs = place &&
|
|
23077
|
+
const abs = place && join15(repoPath, place.shared);
|
|
22635
23078
|
if (!abs || !existsSync17(abs))
|
|
22636
23079
|
return config;
|
|
22637
23080
|
let shared;
|
|
@@ -22649,7 +23092,7 @@ function configText(repoPath, rel, config) {
|
|
|
22649
23092
|
let text;
|
|
22650
23093
|
let was;
|
|
22651
23094
|
try {
|
|
22652
|
-
text = readFileSync12(
|
|
23095
|
+
text = readFileSync12(join15(repoPath, rel), "utf8");
|
|
22653
23096
|
was = parseSettings(text);
|
|
22654
23097
|
} catch {
|
|
22655
23098
|
return fresh;
|
|
@@ -22679,7 +23122,7 @@ function detectConfig(repoPath) {
|
|
|
22679
23122
|
return { config: explicit.config, needsSetup: false };
|
|
22680
23123
|
if (explicit)
|
|
22681
23124
|
log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
|
|
22682
|
-
const pkgPath =
|
|
23125
|
+
const pkgPath = join15(repoPath, "package.json");
|
|
22683
23126
|
if (existsSync17(pkgPath)) {
|
|
22684
23127
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
22685
23128
|
const scripts = pkg.scripts ?? {};
|
|
@@ -22702,7 +23145,7 @@ function detectConfig(repoPath) {
|
|
|
22702
23145
|
};
|
|
22703
23146
|
}
|
|
22704
23147
|
}
|
|
22705
|
-
if (existsSync17(
|
|
23148
|
+
if (existsSync17(join15(repoPath, "start.sh"))) {
|
|
22706
23149
|
return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
|
|
22707
23150
|
}
|
|
22708
23151
|
const assumed = assumeNothingToRun(repoPath);
|
|
@@ -22745,24 +23188,24 @@ function assumeNothingToRun(repoPath) {
|
|
|
22745
23188
|
return marker && !servesPage(repoPath) ? marker : undefined;
|
|
22746
23189
|
}
|
|
22747
23190
|
function markerIn(repoPath) {
|
|
22748
|
-
const named = NO_PAGE_MARKERS.find((f) => existsSync17(
|
|
23191
|
+
const named = NO_PAGE_MARKERS.find((f) => existsSync17(join15(repoPath, f)));
|
|
22749
23192
|
if (named)
|
|
22750
23193
|
return named;
|
|
22751
23194
|
let entries;
|
|
22752
23195
|
try {
|
|
22753
|
-
entries =
|
|
23196
|
+
entries = readdirSync5(repoPath);
|
|
22754
23197
|
} catch {
|
|
22755
23198
|
return;
|
|
22756
23199
|
}
|
|
22757
23200
|
return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
22758
23201
|
}
|
|
22759
23202
|
function servesPage(repoPath) {
|
|
22760
|
-
if (WEB_FILES.some((f) => existsSync17(
|
|
23203
|
+
if (WEB_FILES.some((f) => existsSync17(join15(repoPath, f))))
|
|
22761
23204
|
return true;
|
|
22762
|
-
if (/^web:/m.test(head(
|
|
23205
|
+
if (/^web:/m.test(head(join15(repoPath, "Procfile"))))
|
|
22763
23206
|
return true;
|
|
22764
23207
|
for (const [files, names] of WEB_DEPENDENCIES) {
|
|
22765
|
-
const text = files.map((f) => head(
|
|
23208
|
+
const text = files.map((f) => head(join15(repoPath, f))).join(`
|
|
22766
23209
|
`);
|
|
22767
23210
|
if (text.trim() && names.some((n) => mentions(text, n)))
|
|
22768
23211
|
return true;
|
|
@@ -22772,14 +23215,14 @@ function servesPage(repoPath) {
|
|
|
22772
23215
|
function frontEndBeside(repoPath) {
|
|
22773
23216
|
let dirs;
|
|
22774
23217
|
try {
|
|
22775
|
-
dirs =
|
|
23218
|
+
dirs = readdirSync5(repoPath, { withFileTypes: true });
|
|
22776
23219
|
} catch {
|
|
22777
23220
|
return false;
|
|
22778
23221
|
}
|
|
22779
23222
|
return dirs.some((d) => {
|
|
22780
23223
|
if (!d.isDirectory() || d.name.startsWith(".") || NOT_A_PACKAGE.has(d.name))
|
|
22781
23224
|
return false;
|
|
22782
|
-
const pkg = head(
|
|
23225
|
+
const pkg = head(join15(repoPath, d.name, "package.json"));
|
|
22783
23226
|
if (!pkg)
|
|
22784
23227
|
return false;
|
|
22785
23228
|
try {
|
|
@@ -22835,11 +23278,11 @@ function isLocalDatabase(url) {
|
|
|
22835
23278
|
}
|
|
22836
23279
|
function detectServices(repoPath) {
|
|
22837
23280
|
const out = {};
|
|
22838
|
-
const compose = COMPOSE_FILES.find((f) => existsSync17(
|
|
23281
|
+
const compose = COMPOSE_FILES.find((f) => existsSync17(join15(repoPath, f)));
|
|
22839
23282
|
if (compose)
|
|
22840
23283
|
out.compose = compose;
|
|
22841
23284
|
for (const file of ENV_FILES) {
|
|
22842
|
-
const text = head(
|
|
23285
|
+
const text = head(join15(repoPath, file));
|
|
22843
23286
|
if (!text)
|
|
22844
23287
|
continue;
|
|
22845
23288
|
for (const name of DB_URL_NAMES) {
|
|
@@ -22863,7 +23306,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22863
23306
|
return;
|
|
22864
23307
|
let dir = false;
|
|
22865
23308
|
try {
|
|
22866
|
-
dir = statSync6(
|
|
23309
|
+
dir = statSync6(join15(repoPath, clean)).isDirectory();
|
|
22867
23310
|
} catch {
|
|
22868
23311
|
return;
|
|
22869
23312
|
}
|
|
@@ -22874,25 +23317,25 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22874
23317
|
const t = tokens[i];
|
|
22875
23318
|
if (t === "cd") {
|
|
22876
23319
|
const dir = tokens[i + 1];
|
|
22877
|
-
if (dir && !dir.startsWith("-") && existsSync17(
|
|
23320
|
+
if (dir && !dir.startsWith("-") && existsSync17(join15(repoPath, dir)))
|
|
22878
23321
|
add(dir);
|
|
22879
23322
|
continue;
|
|
22880
23323
|
}
|
|
22881
23324
|
if (t.startsWith("-") || t.startsWith("$"))
|
|
22882
23325
|
continue;
|
|
22883
23326
|
const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
|
|
22884
|
-
if (mod?.[1] && existsSync17(
|
|
23327
|
+
if (mod?.[1] && existsSync17(join15(repoPath, mod[1]))) {
|
|
22885
23328
|
add(mod[1]);
|
|
22886
23329
|
continue;
|
|
22887
23330
|
}
|
|
22888
23331
|
const rel = t.replace(/^\.\//, "");
|
|
22889
23332
|
if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
|
|
22890
23333
|
continue;
|
|
22891
|
-
if (!existsSync17(
|
|
23334
|
+
if (!existsSync17(join15(repoPath, rel)))
|
|
22892
23335
|
continue;
|
|
22893
23336
|
add(rel);
|
|
22894
23337
|
if (depth > 0 && rel.endsWith(".sh")) {
|
|
22895
|
-
for (const p of inferPaths(head(
|
|
23338
|
+
for (const p of inferPaths(head(join15(repoPath, rel)), repoPath, depth - 1))
|
|
22896
23339
|
out.add(p);
|
|
22897
23340
|
}
|
|
22898
23341
|
}
|
|
@@ -22915,9 +23358,9 @@ function procCommand(runner, script, body) {
|
|
|
22915
23358
|
return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
|
|
22916
23359
|
}
|
|
22917
23360
|
function detectPages(repoPath, scripts, runner) {
|
|
22918
|
-
if (!scripts.build || !existsSync17(
|
|
23361
|
+
if (!scripts.build || !existsSync17(join15(repoPath, "functions")))
|
|
22919
23362
|
return null;
|
|
22920
|
-
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));
|
|
22921
23364
|
if (!cfg)
|
|
22922
23365
|
return null;
|
|
22923
23366
|
const out = readFileSync12(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
|
|
@@ -22926,20 +23369,20 @@ function detectPages(repoPath, scripts, runner) {
|
|
|
22926
23369
|
return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
|
|
22927
23370
|
}
|
|
22928
23371
|
function detectRunner(repoPath, packageManager) {
|
|
22929
|
-
if (existsSync17(
|
|
23372
|
+
if (existsSync17(join15(repoPath, "bun.lock")) || existsSync17(join15(repoPath, "bun.lockb")))
|
|
22930
23373
|
return "bun";
|
|
22931
|
-
if (existsSync17(
|
|
23374
|
+
if (existsSync17(join15(repoPath, "pnpm-lock.yaml")))
|
|
22932
23375
|
return "pnpm";
|
|
22933
|
-
if (existsSync17(
|
|
23376
|
+
if (existsSync17(join15(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
|
|
22934
23377
|
return "yarn";
|
|
22935
23378
|
return "npm";
|
|
22936
23379
|
}
|
|
22937
23380
|
|
|
22938
23381
|
// ../daemon/src/repos/create.ts
|
|
22939
|
-
import { existsSync as existsSync18, readdirSync as
|
|
23382
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
22940
23383
|
import { appendFile as appendFile2, mkdir as mkdir2, rm } from "fs/promises";
|
|
22941
23384
|
import { homedir as homedir8 } from "os";
|
|
22942
|
-
import { dirname as dirname6, join as
|
|
23385
|
+
import { dirname as dirname6, join as join16, resolve as resolve8 } from "path";
|
|
22943
23386
|
function isInside(dir, parent) {
|
|
22944
23387
|
const p = parent.replace(/\/+$/, "");
|
|
22945
23388
|
return dir === p || dir.startsWith(`${p}/`);
|
|
@@ -22950,29 +23393,29 @@ function planProject(opts, home = homedir8()) {
|
|
|
22950
23393
|
throw new UserError(nameError);
|
|
22951
23394
|
const first = firstProjectsFolder(opts.parent, home);
|
|
22952
23395
|
const parent = first ?? resolveParent(opts.parent);
|
|
22953
|
-
const dir =
|
|
23396
|
+
const dir = join16(parent, opts.name.trim());
|
|
22954
23397
|
if (existsSync18(dir))
|
|
22955
23398
|
throw new UserError(`${dir} already exists`);
|
|
22956
23399
|
return { parent, dir, makeParent: first !== null };
|
|
22957
23400
|
}
|
|
22958
23401
|
function firstProjectsFolder(raw, home) {
|
|
22959
23402
|
const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
|
|
22960
|
-
if (typed !==
|
|
23403
|
+
if (typed !== join16(home, PROJECTS_FOLDER) || existsSync18(typed) || !existsSync18(home))
|
|
22961
23404
|
return null;
|
|
22962
|
-
return
|
|
23405
|
+
return join16(realpathSync2(home), PROJECTS_FOLDER);
|
|
22963
23406
|
}
|
|
22964
23407
|
function planInPlace(opts) {
|
|
22965
23408
|
const name = opts.name.trim();
|
|
22966
23409
|
if (!name || name === "." || name === ".." || name.includes("/")) {
|
|
22967
23410
|
throw new UserError(`${opts.name} is not a folder name`);
|
|
22968
23411
|
}
|
|
22969
|
-
const typed =
|
|
23412
|
+
const typed = join16(resolveParent(opts.parent), name);
|
|
22970
23413
|
if (!existsSync18(typed) || !statSync7(typed).isDirectory())
|
|
22971
23414
|
throw new UserError(`${typed} is not a folder`);
|
|
22972
23415
|
const dir = realpathSync2(typed);
|
|
22973
|
-
if (existsSync18(
|
|
23416
|
+
if (existsSync18(join16(dir, ".git")))
|
|
22974
23417
|
throw new UserError(`${dir} is already a project; open it instead`);
|
|
22975
|
-
if (!holdsNothing(
|
|
23418
|
+
if (!holdsNothing(readdirSync6(dir))) {
|
|
22976
23419
|
throw new UserError(`${dir} is not empty; only an empty folder can become a project where it is`);
|
|
22977
23420
|
}
|
|
22978
23421
|
return { parent: dirname6(dir), dir, makeParent: false };
|
|
@@ -23061,13 +23504,13 @@ async function undoCreate(dir) {
|
|
|
23061
23504
|
}
|
|
23062
23505
|
async function undoInit(dir) {
|
|
23063
23506
|
try {
|
|
23064
|
-
await rm(
|
|
23507
|
+
await rm(join16(dir, ".git"), { recursive: true, force: true });
|
|
23065
23508
|
} catch (e) {
|
|
23066
23509
|
log.warn(dir, "could not take back a half-made .git", e);
|
|
23067
23510
|
}
|
|
23068
23511
|
}
|
|
23069
23512
|
async function unmakeProject(dir, made) {
|
|
23070
|
-
const target = made === "folder" ? dir :
|
|
23513
|
+
const target = made === "folder" ? dir : join16(dir, ".git");
|
|
23071
23514
|
try {
|
|
23072
23515
|
await rm(target, { recursive: true, force: true });
|
|
23073
23516
|
} catch (e) {
|
|
@@ -23098,7 +23541,7 @@ async function ok(p, what) {
|
|
|
23098
23541
|
|
|
23099
23542
|
// ../daemon/src/repos/watcher.ts
|
|
23100
23543
|
import { existsSync as existsSync19, watch } from "fs";
|
|
23101
|
-
import { basename as basename4, join as
|
|
23544
|
+
import { basename as basename4, join as join17 } from "path";
|
|
23102
23545
|
function watchDefaultBranch(repoPath, branch, onMove) {
|
|
23103
23546
|
const last = { local: null, upstream: null };
|
|
23104
23547
|
const read = async () => {
|
|
@@ -23151,13 +23594,13 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
23151
23594
|
]);
|
|
23152
23595
|
if (stopped)
|
|
23153
23596
|
return;
|
|
23154
|
-
const gitDir = common.ok && common.out ? common.out :
|
|
23597
|
+
const gitDir = common.ok && common.out ? common.out : join17(repoPath, ".git");
|
|
23155
23598
|
tryWatch(gitDir);
|
|
23156
|
-
tryWatch(
|
|
23599
|
+
tryWatch(join17(gitDir, "refs", "heads"));
|
|
23157
23600
|
if (!remote.ok || !remote.out)
|
|
23158
23601
|
return;
|
|
23159
|
-
const remotes =
|
|
23160
|
-
const mine =
|
|
23602
|
+
const remotes = join17(gitDir, "refs", "remotes");
|
|
23603
|
+
const mine = join17(remotes, remote.out);
|
|
23161
23604
|
let inner = null;
|
|
23162
23605
|
let mid = null;
|
|
23163
23606
|
const armInner = () => {
|
|
@@ -23173,7 +23616,7 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
23173
23616
|
}) : null;
|
|
23174
23617
|
armInner();
|
|
23175
23618
|
};
|
|
23176
|
-
tryWatch(
|
|
23619
|
+
tryWatch(join17(gitDir, "refs"), (_event, filename) => {
|
|
23177
23620
|
if (!filename || filename === "remotes")
|
|
23178
23621
|
armMid();
|
|
23179
23622
|
schedule();
|
|
@@ -23202,7 +23645,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
23202
23645
|
}, 300);
|
|
23203
23646
|
};
|
|
23204
23647
|
const arm = (commonDir) => {
|
|
23205
|
-
const dir =
|
|
23648
|
+
const dir = join17(commonDir, "worktrees");
|
|
23206
23649
|
const armInner = () => {
|
|
23207
23650
|
inner?.close();
|
|
23208
23651
|
inner = null;
|
|
@@ -23245,7 +23688,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
23245
23688
|
function watchConfigFile(repoPath, onChange) {
|
|
23246
23689
|
const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
|
|
23247
23690
|
const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename4(rel)));
|
|
23248
|
-
const dir =
|
|
23691
|
+
const dir = join17(repoPath, CONFIG_DIR);
|
|
23249
23692
|
let timer = null;
|
|
23250
23693
|
let outer = null;
|
|
23251
23694
|
let inner = null;
|
|
@@ -23382,7 +23825,7 @@ class RepoRegistry {
|
|
|
23382
23825
|
return `${repo.name} is set up now`;
|
|
23383
23826
|
if (main.promptedAt || main.lastTurn)
|
|
23384
23827
|
return `${repo.name} has a chat now`;
|
|
23385
|
-
if (existsSync20(
|
|
23828
|
+
if (existsSync20(join18(repo.path, ".claude")))
|
|
23386
23829
|
return `an agent has already run in ${repo.name}`;
|
|
23387
23830
|
if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
|
|
23388
23831
|
return `${repo.name} has files in it now`;
|
|
@@ -23533,10 +23976,10 @@ class RepoRegistry {
|
|
|
23533
23976
|
throw new UserError(current.reason);
|
|
23534
23977
|
const was = configTarget(repo.path, !!repo.made);
|
|
23535
23978
|
const rel = configSibling(was, kind);
|
|
23536
|
-
const moved = rel !== was && existsSync20(
|
|
23979
|
+
const moved = rel !== was && existsSync20(join18(repo.path, was));
|
|
23537
23980
|
if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
|
|
23538
23981
|
try {
|
|
23539
|
-
|
|
23982
|
+
rmSync3(join18(repo.path, was));
|
|
23540
23983
|
} catch (e) {
|
|
23541
23984
|
log.warn(repoId, `could not remove ${was}`, e);
|
|
23542
23985
|
}
|
|
@@ -23545,9 +23988,9 @@ class RepoRegistry {
|
|
|
23545
23988
|
}
|
|
23546
23989
|
if (isLocalConfigFile(rel))
|
|
23547
23990
|
await excludeFromGit(repo.path, rel);
|
|
23548
|
-
const file =
|
|
23991
|
+
const file = join18(repo.path, rel);
|
|
23549
23992
|
try {
|
|
23550
|
-
|
|
23993
|
+
mkdirSync7(dirname7(file), { recursive: true });
|
|
23551
23994
|
writeFileSync7(file, configText(repo.path, rel, config));
|
|
23552
23995
|
} catch (e) {
|
|
23553
23996
|
log.warn(repoId, `could not write ${rel}`, e);
|
|
@@ -23649,7 +24092,7 @@ class RepoRegistry {
|
|
|
23649
24092
|
}
|
|
23650
24093
|
placeConfig(repo) {
|
|
23651
24094
|
const rel = configTarget(repo.path, !!repo.made);
|
|
23652
|
-
if (isLocalConfigFile(rel) && existsSync20(
|
|
24095
|
+
if (isLocalConfigFile(rel) && existsSync20(join18(repo.path, rel))) {
|
|
23653
24096
|
fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
|
|
23654
24097
|
}
|
|
23655
24098
|
if (repo.configFile === rel)
|
|
@@ -23741,7 +24184,7 @@ class RepoRegistry {
|
|
|
23741
24184
|
}
|
|
23742
24185
|
|
|
23743
24186
|
// ../daemon/src/routes/service.ts
|
|
23744
|
-
import { join as
|
|
24187
|
+
import { join as join19 } from "path";
|
|
23745
24188
|
|
|
23746
24189
|
// ../daemon/src/routes/fileRouters.ts
|
|
23747
24190
|
var SKIP = /(^|\/)(node_modules|dist|build|out|coverage|\.next|\.nuxt|\.output|\.svelte-kit|\.vercel|\.netlify)\//;
|
|
@@ -24941,7 +25384,7 @@ class RouteService {
|
|
|
24941
25384
|
return mergeRoutes(fileRoutes(paths, roots), code);
|
|
24942
25385
|
}
|
|
24943
25386
|
async cachedRead(root, path, had, kept, decode) {
|
|
24944
|
-
const abs =
|
|
25387
|
+
const abs = join19(root, path);
|
|
24945
25388
|
const stat = await this.fs.stat(abs);
|
|
24946
25389
|
if (!stat)
|
|
24947
25390
|
return null;
|
|
@@ -24966,7 +25409,7 @@ class RouteService {
|
|
|
24966
25409
|
}
|
|
24967
25410
|
}
|
|
24968
25411
|
async hash(root, file) {
|
|
24969
|
-
const abs =
|
|
25412
|
+
const abs = join19(root, file);
|
|
24970
25413
|
const stat = await this.fs.stat(abs);
|
|
24971
25414
|
if (!stat)
|
|
24972
25415
|
return null;
|
|
@@ -25288,7 +25731,7 @@ function servedLocally(origin) {
|
|
|
25288
25731
|
import { homedir as homedir11 } from "os";
|
|
25289
25732
|
|
|
25290
25733
|
// ../daemon/src/core/pair.ts
|
|
25291
|
-
import { randomBytes as
|
|
25734
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
25292
25735
|
class PairCodes {
|
|
25293
25736
|
now;
|
|
25294
25737
|
live = null;
|
|
@@ -25296,7 +25739,7 @@ class PairCodes {
|
|
|
25296
25739
|
this.now = now;
|
|
25297
25740
|
}
|
|
25298
25741
|
mint() {
|
|
25299
|
-
const code =
|
|
25742
|
+
const code = randomBytes4(9).toString("base64url");
|
|
25300
25743
|
this.live = { code, until: this.now() + PAIR_TTL_MS };
|
|
25301
25744
|
return { code, ms: PAIR_TTL_MS };
|
|
25302
25745
|
}
|
|
@@ -25307,10 +25750,13 @@ class PairCodes {
|
|
|
25307
25750
|
}
|
|
25308
25751
|
}
|
|
25309
25752
|
|
|
25753
|
+
// ../daemon/src/server/handlers.ts
|
|
25754
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
25755
|
+
|
|
25310
25756
|
// ../daemon/src/agent/config.ts
|
|
25311
25757
|
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
|
|
25312
25758
|
import { homedir as homedir10 } from "os";
|
|
25313
|
-
import { join as
|
|
25759
|
+
import { join as join20 } from "path";
|
|
25314
25760
|
function agentConfigFiles(spec, repoPath, home = homedir10()) {
|
|
25315
25761
|
const file = (id, label, path) => ({
|
|
25316
25762
|
id,
|
|
@@ -25320,30 +25766,30 @@ function agentConfigFiles(spec, repoPath, home = homedir10()) {
|
|
|
25320
25766
|
});
|
|
25321
25767
|
if (spec.id === "claude") {
|
|
25322
25768
|
return [
|
|
25323
|
-
file("user-config", "user config (MCP servers, projects)",
|
|
25324
|
-
file("user-settings", "user settings (permissions, hooks)",
|
|
25325
|
-
...repoPath ? [file("project-mcp", "project MCP servers",
|
|
25326
|
-
...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"))] : []
|
|
25327
25773
|
];
|
|
25328
25774
|
}
|
|
25329
25775
|
if (spec.id === "codex")
|
|
25330
|
-
return [file("user-config", "config",
|
|
25776
|
+
return [file("user-config", "config", join20(home, ".codex", "config.toml"))];
|
|
25331
25777
|
return [];
|
|
25332
25778
|
}
|
|
25333
25779
|
function describeAgentConfig(spec, repoPath, home = homedir10()) {
|
|
25334
25780
|
const files = agentConfigFiles(spec, repoPath, home);
|
|
25335
25781
|
const servers = [];
|
|
25336
25782
|
if (spec.id === "claude") {
|
|
25337
|
-
const user = readJson(
|
|
25783
|
+
const user = readJson(join20(home, ".claude.json"));
|
|
25338
25784
|
servers.push(...serversOf(user?.mcpServers, "user"));
|
|
25339
25785
|
if (repoPath) {
|
|
25340
25786
|
const projects = user?.projects;
|
|
25341
25787
|
const mine = projects?.[repoPath];
|
|
25342
25788
|
servers.push(...serversOf(mine?.mcpServers, "local"));
|
|
25343
|
-
servers.push(...serversOf(readJson(
|
|
25789
|
+
servers.push(...serversOf(readJson(join20(repoPath, ".mcp.json"))?.mcpServers, "project"));
|
|
25344
25790
|
}
|
|
25345
25791
|
} else if (spec.id === "codex") {
|
|
25346
|
-
servers.push(...codexServers(
|
|
25792
|
+
servers.push(...codexServers(join20(home, ".codex", "config.toml")));
|
|
25347
25793
|
}
|
|
25348
25794
|
return { agent: spec.id, files, servers };
|
|
25349
25795
|
}
|
|
@@ -25981,6 +26427,9 @@ var handlers = {
|
|
|
25981
26427
|
s.drafts.set(msg.boxId, msg.text, msg.clientId);
|
|
25982
26428
|
s.worktrees.spare.typed(msg.boxId, msg.text);
|
|
25983
26429
|
},
|
|
26430
|
+
"set-attachments"(msg, _ctx, s) {
|
|
26431
|
+
s.drafts.setAttachments(msg.boxId, msg.items, msg.clientId);
|
|
26432
|
+
},
|
|
25984
26433
|
"refresh-git"(msg, _ctx, s) {
|
|
25985
26434
|
s.worktrees.recount(msg.repoId);
|
|
25986
26435
|
},
|
|
@@ -25993,19 +26442,17 @@ var handlers = {
|
|
|
25993
26442
|
"forget-visit"(msg, _ctx, s) {
|
|
25994
26443
|
s.routes.forget(msg.repoId, msg.path);
|
|
25995
26444
|
},
|
|
25996
|
-
async chat(msg,
|
|
25997
|
-
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, {
|
|
25998
26447
|
text: msg.text,
|
|
25999
26448
|
clientId: msg.clientId,
|
|
26000
26449
|
context: msg.context,
|
|
26001
26450
|
attachments: msg.attachments
|
|
26002
|
-
});
|
|
26451
|
+
}));
|
|
26003
26452
|
},
|
|
26004
|
-
async "create-worktree"(msg,
|
|
26005
|
-
|
|
26453
|
+
async "create-worktree"(msg, ctx, s) {
|
|
26454
|
+
const opts = {
|
|
26006
26455
|
createdBy: msg.clientId,
|
|
26007
|
-
worktreeId: msg.worktreeId,
|
|
26008
|
-
variant: msg.variant,
|
|
26009
26456
|
context: msg.context,
|
|
26010
26457
|
attachments: msg.attachments,
|
|
26011
26458
|
agent: msg.agent,
|
|
@@ -26014,6 +26461,30 @@ var handlers = {
|
|
|
26014
26461
|
model: msg.model,
|
|
26015
26462
|
effort: msg.effort,
|
|
26016
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];
|
|
26017
26488
|
});
|
|
26018
26489
|
},
|
|
26019
26490
|
"set-worktree-profile"(msg, _ctx, s) {
|
|
@@ -26036,6 +26507,8 @@ var handlers = {
|
|
|
26036
26507
|
},
|
|
26037
26508
|
"batch-worktrees"(msg, ctx, s) {
|
|
26038
26509
|
const repo = s.state.requireRepo(msg.repoId);
|
|
26510
|
+
if (msg.boxId)
|
|
26511
|
+
s.drafts.take(msg.boxId);
|
|
26039
26512
|
fireAndForget(msg.repoId, (async () => {
|
|
26040
26513
|
const agent = msg.agent ?? s.state.defaultAgent ?? DEFAULT_AGENT_ID;
|
|
26041
26514
|
const tasks = await s.planTasks(msg.prompt, repo.path, agent) ?? [msg.prompt];
|
|
@@ -26059,8 +26532,10 @@ var handlers = {
|
|
|
26059
26532
|
"list-archived"(msg, ctx, s) {
|
|
26060
26533
|
ctx.reply({ t: "archived", repoId: msg.repoId, items: s.worktrees.archived(msg.repoId) });
|
|
26061
26534
|
},
|
|
26062
|
-
async "restore-worktree"(msg,
|
|
26063
|
-
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
|
+
});
|
|
26064
26539
|
},
|
|
26065
26540
|
async "delete-archived"(msg, _ctx, s) {
|
|
26066
26541
|
await s.worktrees.deleteArchived(msg.archiveId);
|
|
@@ -26072,10 +26547,17 @@ var handlers = {
|
|
|
26072
26547
|
await gitStatus(s, ctx, msg.worktreeId);
|
|
26073
26548
|
},
|
|
26074
26549
|
async "read-file"(msg, ctx, s) {
|
|
26075
|
-
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
|
+
};
|
|
26076
26558
|
try {
|
|
26077
26559
|
const kept = s.worktrees.readable(msg.worktreeId) ? null : await s.worktrees.archivedFile(msg.worktreeId, msg.path, msg.ref);
|
|
26078
|
-
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);
|
|
26079
26561
|
ctx.reply({ ...head, ...read });
|
|
26080
26562
|
} catch (e) {
|
|
26081
26563
|
if (!(e instanceof UserError))
|
|
@@ -26188,7 +26670,21 @@ var handlers = {
|
|
|
26188
26670
|
},
|
|
26189
26671
|
unqueue(msg, _ctx, s) {
|
|
26190
26672
|
requireRun(s, msg.worktreeId);
|
|
26191
|
-
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 });
|
|
26192
26688
|
},
|
|
26193
26689
|
async "changed-ranges"(msg, ctx, s) {
|
|
26194
26690
|
const { ranges, lineOffset } = await s.files.changedRanges(msg.worktreeId, msg.path);
|
|
@@ -26388,6 +26884,10 @@ var handlers = {
|
|
|
26388
26884
|
if (!s.runs.stop(msg.worktreeId, msg.kind))
|
|
26389
26885
|
throw new UserError(`no ${msg.kind} is running here`);
|
|
26390
26886
|
},
|
|
26887
|
+
"preview-start"(msg, _ctx, s) {
|
|
26888
|
+
requireRun(s, msg.worktreeId);
|
|
26889
|
+
s.worktrees.startPreview(msg.worktreeId);
|
|
26890
|
+
},
|
|
26391
26891
|
async "search-refs"(msg, ctx, s) {
|
|
26392
26892
|
const refs = await s.refs.search(msg.repoId, msg.query);
|
|
26393
26893
|
ctx.reply({ t: "refs", repoId: msg.repoId, query: msg.query, refs });
|
|
@@ -26400,6 +26900,30 @@ var handlers = {
|
|
|
26400
26900
|
await s.worktrees.openRef(msg.repoId, msg.kind, msg.ref, { createdBy: msg.clientId, pr: msg.pr });
|
|
26401
26901
|
}
|
|
26402
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
|
+
}
|
|
26403
26927
|
function looseCwd(s, id, stream) {
|
|
26404
26928
|
const disc = s.worktrees.discoveredById(id);
|
|
26405
26929
|
if (!disc)
|
|
@@ -26425,9 +26949,16 @@ async function dispatch(msg, ctx, s) {
|
|
|
26425
26949
|
|
|
26426
26950
|
// ../daemon/src/server/http.ts
|
|
26427
26951
|
import { existsSync as existsSync23 } from "fs";
|
|
26428
|
-
import { join as
|
|
26952
|
+
import { join as join21 } from "path";
|
|
26429
26953
|
var IMMUTABLE = "private, max-age=31536000, immutable";
|
|
26430
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
|
+
}
|
|
26431
26962
|
function createFetch(opts) {
|
|
26432
26963
|
const grant = previewGrant(opts.token);
|
|
26433
26964
|
const remote = opts.remote;
|
|
@@ -26561,6 +27092,37 @@ function createFetch(opts) {
|
|
|
26561
27092
|
return new Response("open failed; see daemon log", { status: 500 });
|
|
26562
27093
|
}
|
|
26563
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
|
+
}
|
|
26564
27126
|
if (url.pathname.startsWith("/attachments/")) {
|
|
26565
27127
|
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
26566
27128
|
return new Response("unauthorized", { status: 401 });
|
|
@@ -26571,7 +27133,9 @@ function createFetch(opts) {
|
|
|
26571
27133
|
const path = places.find((p) => !!p && existsSync23(p));
|
|
26572
27134
|
if (!path)
|
|
26573
27135
|
return new Response("not found", { status: 404 });
|
|
26574
|
-
return new Response(Bun.file(path), {
|
|
27136
|
+
return new Response(Bun.file(path), {
|
|
27137
|
+
headers: { "cache-control": IMMUTABLE, ...attachedHeaders(drawnType(file ?? "")) }
|
|
27138
|
+
});
|
|
26575
27139
|
}
|
|
26576
27140
|
if (url.pathname.startsWith("/files/")) {
|
|
26577
27141
|
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
@@ -26599,14 +27163,14 @@ function createFetch(opts) {
|
|
|
26599
27163
|
});
|
|
26600
27164
|
}
|
|
26601
27165
|
const rel = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
26602
|
-
const file =
|
|
27166
|
+
const file = join21(opts.shellDist, rel.replaceAll("..", ""));
|
|
26603
27167
|
const hashed = rel.startsWith("/assets/");
|
|
26604
27168
|
if (existsSync23(file) && Bun.file(file).size > 0) {
|
|
26605
27169
|
return new Response(Bun.file(file), { headers: { "cache-control": hashed ? IMMUTABLE : NO_STORE } });
|
|
26606
27170
|
}
|
|
26607
27171
|
if (hashed)
|
|
26608
27172
|
return new Response("not found", { status: 404 });
|
|
26609
|
-
const index =
|
|
27173
|
+
const index = join21(opts.shellDist, "index.html");
|
|
26610
27174
|
if (existsSync23(index))
|
|
26611
27175
|
return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE } });
|
|
26612
27176
|
return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
|
|
@@ -26618,6 +27182,22 @@ var DROP_ABOVE_BYTES = 4 * 1024 * 1024;
|
|
|
26618
27182
|
var DROPPED_NOTICE = `\r
|
|
26619
27183
|
[toyon] output dropped: this pane was too far behind\r
|
|
26620
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
|
+
}
|
|
26621
27201
|
function startServer(opts) {
|
|
26622
27202
|
const { services: s, token, version } = opts;
|
|
26623
27203
|
const sockets = new Set;
|
|
@@ -26843,6 +27423,7 @@ function startServer(opts) {
|
|
|
26843
27423
|
s.hub.on("visitsChanged", (repoId) => broadcast({ t: "visits", repoId, pages: s.routes.history(repoId) }));
|
|
26844
27424
|
s.hub.on("archiveChanged", (repoId) => broadcast({ t: "archived", repoId, items: s.worktrees.archived(repoId) }));
|
|
26845
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 } : {} }));
|
|
26846
27427
|
const helloFrame = async () => {
|
|
26847
27428
|
await s.update.refresh();
|
|
26848
27429
|
return {
|
|
@@ -26869,11 +27450,13 @@ function startServer(opts) {
|
|
|
26869
27450
|
self: s.self.get(),
|
|
26870
27451
|
update: s.update.get(),
|
|
26871
27452
|
drafts: s.drafts.all(),
|
|
27453
|
+
attachments: s.drafts.allAttachments(),
|
|
26872
27454
|
managed: managedView(opts.managed)
|
|
26873
27455
|
};
|
|
26874
27456
|
};
|
|
26875
27457
|
let branded = false;
|
|
26876
27458
|
const serverConfig = {
|
|
27459
|
+
maxRequestBodySize: UPLOAD_MAX_BYTES,
|
|
26877
27460
|
hostname: cloud.bindHost,
|
|
26878
27461
|
fetch: createFetch({
|
|
26879
27462
|
token,
|
|
@@ -26901,7 +27484,6 @@ function startServer(opts) {
|
|
|
26901
27484
|
}
|
|
26902
27485
|
}),
|
|
26903
27486
|
websocket: {
|
|
26904
|
-
maxPayloadLength: 64 * 1024 * 1024,
|
|
26905
27487
|
async open(ws) {
|
|
26906
27488
|
const preview = ws.data.preview;
|
|
26907
27489
|
if (preview) {
|
|
@@ -26943,7 +27525,7 @@ function startServer(opts) {
|
|
|
26943
27525
|
}
|
|
26944
27526
|
const parsed = parseClientMsg(json);
|
|
26945
27527
|
if (!parsed.ok) {
|
|
26946
|
-
send(ws,
|
|
27528
|
+
send(ws, unparsedReply(json, parsed.reason, s.drafts));
|
|
26947
27529
|
return;
|
|
26948
27530
|
}
|
|
26949
27531
|
const ctx = {
|
|
@@ -27010,16 +27592,16 @@ function startServer(opts) {
|
|
|
27010
27592
|
}
|
|
27011
27593
|
|
|
27012
27594
|
// ../daemon/src/themes/store.ts
|
|
27013
|
-
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";
|
|
27014
27596
|
import { homedir as homedir12 } from "os";
|
|
27015
|
-
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";
|
|
27016
27598
|
var home = homedir12();
|
|
27017
27599
|
var defaultExtensionDirs = [
|
|
27018
|
-
|
|
27019
|
-
|
|
27020
|
-
|
|
27021
|
-
|
|
27022
|
-
|
|
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"),
|
|
27023
27605
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/extensions",
|
|
27024
27606
|
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions",
|
|
27025
27607
|
"/Applications/Cursor.app/Contents/Resources/app/extensions",
|
|
@@ -27103,7 +27685,7 @@ class ThemeStore {
|
|
|
27103
27685
|
name: json?.name ?? base,
|
|
27104
27686
|
source: "file"
|
|
27105
27687
|
});
|
|
27106
|
-
writeFileSync8(
|
|
27688
|
+
writeFileSync8(join22(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
|
|
27107
27689
|
`);
|
|
27108
27690
|
this.load();
|
|
27109
27691
|
return theme;
|
|
@@ -27135,10 +27717,10 @@ function loadDir(dir) {
|
|
|
27135
27717
|
if (!existsSync24(dir))
|
|
27136
27718
|
return [];
|
|
27137
27719
|
const out = [];
|
|
27138
|
-
for (const f of
|
|
27720
|
+
for (const f of readdirSync7(dir)) {
|
|
27139
27721
|
if (!/\.jsonc?$/i.test(f))
|
|
27140
27722
|
continue;
|
|
27141
|
-
const file =
|
|
27723
|
+
const file = join22(dir, f);
|
|
27142
27724
|
const id = `file:${slug(f.replace(/\.(json|jsonc)$/i, ""))}`;
|
|
27143
27725
|
try {
|
|
27144
27726
|
const json = readJsonc(file);
|
|
@@ -27159,13 +27741,13 @@ function discover(dir) {
|
|
|
27159
27741
|
const out = [];
|
|
27160
27742
|
let entries;
|
|
27161
27743
|
try {
|
|
27162
|
-
entries =
|
|
27744
|
+
entries = readdirSync7(dir);
|
|
27163
27745
|
} catch {
|
|
27164
27746
|
return out;
|
|
27165
27747
|
}
|
|
27166
27748
|
for (const name of entries) {
|
|
27167
|
-
const extDir =
|
|
27168
|
-
const pkgFile =
|
|
27749
|
+
const extDir = join22(dir, name);
|
|
27750
|
+
const pkgFile = join22(extDir, "package.json");
|
|
27169
27751
|
try {
|
|
27170
27752
|
if (!statSync9(extDir).isDirectory() || !existsSync24(pkgFile))
|
|
27171
27753
|
continue;
|
|
@@ -27175,7 +27757,7 @@ function discover(dir) {
|
|
|
27175
27757
|
continue;
|
|
27176
27758
|
const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
|
|
27177
27759
|
let nls = {};
|
|
27178
|
-
const nlsFile =
|
|
27760
|
+
const nlsFile = join22(extDir, "package.nls.json");
|
|
27179
27761
|
if (existsSync24(nlsFile)) {
|
|
27180
27762
|
try {
|
|
27181
27763
|
nls = JSON.parse(readFileSync15(nlsFile, "utf8"));
|
|
@@ -27591,8 +28173,8 @@ class BackendShare {
|
|
|
27591
28173
|
// ../daemon/src/worktrees/cache.ts
|
|
27592
28174
|
import { createHash as createHash3 } from "crypto";
|
|
27593
28175
|
import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
|
|
27594
|
-
import { mkdir as mkdir4, readdir as readdir2, rename, rm as rm3, stat as
|
|
27595
|
-
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";
|
|
27596
28178
|
|
|
27597
28179
|
// ../daemon/src/core/clone.ts
|
|
27598
28180
|
import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
@@ -27623,7 +28205,7 @@ var STALE_MS = 30 * 24 * 3600000;
|
|
|
27623
28205
|
var TMP = ".tmp-";
|
|
27624
28206
|
var MANIFEST = "manifest.json";
|
|
27625
28207
|
function defaultTools(dir) {
|
|
27626
|
-
const has = (...files) => files.some((f) => existsSync25(
|
|
28208
|
+
const has = (...files) => files.some((f) => existsSync25(join23(dir, f)));
|
|
27627
28209
|
const out = [];
|
|
27628
28210
|
if (has("bun.lock", "bun.lockb"))
|
|
27629
28211
|
out.push("bun --version");
|
|
@@ -27636,7 +28218,7 @@ function defaultTools(dir) {
|
|
|
27636
28218
|
return out;
|
|
27637
28219
|
}
|
|
27638
28220
|
function withSidecars(root, rel) {
|
|
27639
|
-
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(
|
|
28221
|
+
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(join23(root, n)));
|
|
27640
28222
|
}
|
|
27641
28223
|
var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
|
|
27642
28224
|
async function listEntries(familyDir) {
|
|
@@ -27651,8 +28233,8 @@ async function listEntries(familyDir) {
|
|
|
27651
28233
|
const m = /^([0-9a-f]{12})-([0-9a-z]+)$/.exec(name);
|
|
27652
28234
|
if (!m?.[1] || !m[2])
|
|
27653
28235
|
continue;
|
|
27654
|
-
const dir =
|
|
27655
|
-
if (!existsSync25(
|
|
28236
|
+
const dir = join23(familyDir, name);
|
|
28237
|
+
if (!existsSync25(join23(dir, MANIFEST)))
|
|
27656
28238
|
continue;
|
|
27657
28239
|
out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
|
|
27658
28240
|
}
|
|
@@ -27682,7 +28264,7 @@ class ArtifactCache {
|
|
|
27682
28264
|
try {
|
|
27683
28265
|
const family = await this.family(wt, repo, policy);
|
|
27684
28266
|
const base = await this.baseCommit(wt, repo);
|
|
27685
|
-
const entries = await listEntries(
|
|
28267
|
+
const entries = await listEntries(join23(this.d.paths.cacheDir, repo.id, family.key));
|
|
27686
28268
|
const entry = entries.find((e) => e.base === base.slice(0, 12)) ?? entries[0];
|
|
27687
28269
|
if (!entry) {
|
|
27688
28270
|
say(`cache: nothing kept for these lockfiles and tools on ${family.platform}`);
|
|
@@ -27690,14 +28272,14 @@ class ArtifactCache {
|
|
|
27690
28272
|
}
|
|
27691
28273
|
const restored = [];
|
|
27692
28274
|
for (const rel of policy.paths) {
|
|
27693
|
-
if (existsSync25(
|
|
28275
|
+
if (existsSync25(join23(wt.path, rel)))
|
|
27694
28276
|
continue;
|
|
27695
28277
|
const names = withSidecars(entry.dir, rel);
|
|
27696
28278
|
if (names.length === 0)
|
|
27697
28279
|
continue;
|
|
27698
28280
|
let whole = true;
|
|
27699
28281
|
for (const name of names) {
|
|
27700
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
28282
|
+
const r = await (this.d.clone ?? cloneTree)(join23(entry.dir, name), join23(wt.path, name));
|
|
27701
28283
|
if (r.ok)
|
|
27702
28284
|
continue;
|
|
27703
28285
|
say(`cache: could not restore ${name}: ${r.error}`);
|
|
@@ -27731,12 +28313,12 @@ class ArtifactCache {
|
|
|
27731
28313
|
let tmp;
|
|
27732
28314
|
let slot;
|
|
27733
28315
|
try {
|
|
27734
|
-
const present = policy.paths.filter((rel) => existsSync25(
|
|
28316
|
+
const present = policy.paths.filter((rel) => existsSync25(join23(wt.path, rel)));
|
|
27735
28317
|
if (present.length === 0)
|
|
27736
28318
|
return;
|
|
27737
28319
|
const family = await this.family(wt, repo, policy);
|
|
27738
28320
|
const base = await this.baseCommit(wt, repo);
|
|
27739
|
-
const familyDir =
|
|
28321
|
+
const familyDir = join23(this.d.paths.cacheDir, repo.id, family.key);
|
|
27740
28322
|
const key = `${familyDir}/${base.slice(0, 12)}`;
|
|
27741
28323
|
const kept = (await listEntries(familyDir)).find((e) => e.base === base.slice(0, 12));
|
|
27742
28324
|
if (kept || this.making.has(key)) {
|
|
@@ -27747,21 +28329,21 @@ class ArtifactCache {
|
|
|
27747
28329
|
this.making.add(key);
|
|
27748
28330
|
const at = Date.now();
|
|
27749
28331
|
const name = entryName(base, at);
|
|
27750
|
-
tmp =
|
|
28332
|
+
tmp = join23(familyDir, `${TMP}${name}`);
|
|
27751
28333
|
await mkdir4(tmp, { recursive: true });
|
|
27752
28334
|
let how = "clonefile";
|
|
27753
28335
|
for (const rel of present) {
|
|
27754
28336
|
for (const n of withSidecars(wt.path, rel)) {
|
|
27755
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
28337
|
+
const r = await (this.d.clone ?? cloneTree)(join23(wt.path, n), join23(tmp, n));
|
|
27756
28338
|
if (!r.ok)
|
|
27757
28339
|
throw new Error(`could not keep ${n}: ${r.error}`);
|
|
27758
28340
|
how = r.how;
|
|
27759
28341
|
}
|
|
27760
28342
|
}
|
|
27761
28343
|
const manifest = { at, base, platform: family.platform, tools: family.tools, paths: present };
|
|
27762
|
-
await writeFile2(
|
|
28344
|
+
await writeFile2(join23(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
|
|
27763
28345
|
`);
|
|
27764
|
-
await rename(tmp,
|
|
28346
|
+
await rename(tmp, join23(familyDir, name));
|
|
27765
28347
|
tmp = undefined;
|
|
27766
28348
|
log.info(worktreeId, `cache: kept ${list(present)} for ${base.slice(0, 7)} via ${how} in ${Date.now() - started}ms`);
|
|
27767
28349
|
await this.prune(familyDir);
|
|
@@ -27789,7 +28371,7 @@ class ArtifactCache {
|
|
|
27789
28371
|
return;
|
|
27790
28372
|
}
|
|
27791
28373
|
for (const repoId of repoIds) {
|
|
27792
|
-
const repoDir =
|
|
28374
|
+
const repoDir = join23(root, repoId);
|
|
27793
28375
|
if (!this.d.state.repo(repoId)) {
|
|
27794
28376
|
await rm3(repoDir, { recursive: true, force: true });
|
|
27795
28377
|
continue;
|
|
@@ -27801,7 +28383,7 @@ class ArtifactCache {
|
|
|
27801
28383
|
continue;
|
|
27802
28384
|
}
|
|
27803
28385
|
for (const family of families) {
|
|
27804
|
-
const familyDir =
|
|
28386
|
+
const familyDir = join23(repoDir, family);
|
|
27805
28387
|
let names;
|
|
27806
28388
|
try {
|
|
27807
28389
|
names = await readdir2(familyDir);
|
|
@@ -27810,11 +28392,11 @@ class ArtifactCache {
|
|
|
27810
28392
|
}
|
|
27811
28393
|
for (const n of names)
|
|
27812
28394
|
if (n.startsWith(TMP))
|
|
27813
|
-
await rm3(
|
|
28395
|
+
await rm3(join23(familyDir, n), { recursive: true, force: true });
|
|
27814
28396
|
const entries = await listEntries(familyDir);
|
|
27815
28397
|
let newest = 0;
|
|
27816
28398
|
for (const e of entries) {
|
|
27817
|
-
const s = await
|
|
28399
|
+
const s = await stat4(e.dir).catch(() => null);
|
|
27818
28400
|
if (s)
|
|
27819
28401
|
newest = Math.max(newest, s.mtimeMs);
|
|
27820
28402
|
}
|
|
@@ -27830,7 +28412,7 @@ class ArtifactCache {
|
|
|
27830
28412
|
for (const f of policy.key) {
|
|
27831
28413
|
h.update(`\x00${f}\x00`);
|
|
27832
28414
|
try {
|
|
27833
|
-
h.update(readFileSync16(
|
|
28415
|
+
h.update(readFileSync16(join23(wt.path, f)));
|
|
27834
28416
|
} catch {
|
|
27835
28417
|
h.update("(absent)");
|
|
27836
28418
|
}
|
|
@@ -28660,12 +29242,12 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
|
|
|
28660
29242
|
}
|
|
28661
29243
|
|
|
28662
29244
|
// ../daemon/src/worktrees/service.ts
|
|
28663
|
-
import { cpSync, existsSync as
|
|
29245
|
+
import { cpSync, existsSync as existsSync29, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
|
|
28664
29246
|
import { rm as rm4 } from "fs/promises";
|
|
28665
|
-
import { dirname as dirname11, join as
|
|
29247
|
+
import { dirname as dirname11, join as join27 } from "path";
|
|
28666
29248
|
|
|
28667
29249
|
// ../daemon/src/git/archive.ts
|
|
28668
|
-
import { rmSync as
|
|
29250
|
+
import { rmSync as rmSync4 } from "fs";
|
|
28669
29251
|
var archiveRef = (worktreeId) => `refs/toyon/archive/${worktreeId}`;
|
|
28670
29252
|
var SNAPSHOT_SUBJECT = "toyon: uncommitted work";
|
|
28671
29253
|
var SNAPSHOT_AUTHOR = "toyon";
|
|
@@ -28708,7 +29290,7 @@ async function snapshotCommit(wtPath, head, indexFile) {
|
|
|
28708
29290
|
], wtPath);
|
|
28709
29291
|
return commit.ok ? commit.out : null;
|
|
28710
29292
|
} finally {
|
|
28711
|
-
|
|
29293
|
+
rmSync4(indexFile, { force: true });
|
|
28712
29294
|
}
|
|
28713
29295
|
}
|
|
28714
29296
|
async function keptAt(repoPath, ref) {
|
|
@@ -29183,18 +29765,142 @@ function compareUrl(remoteUrl, base, branch) {
|
|
|
29183
29765
|
return `https://github.com/${m[1]}/compare/${encodeURIComponent(base)}...${encodeURIComponent(branch)}?expand=1`;
|
|
29184
29766
|
}
|
|
29185
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
|
+
|
|
29186
29892
|
// ../daemon/src/worktrees/archive.ts
|
|
29187
|
-
import { existsSync as
|
|
29188
|
-
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";
|
|
29189
29895
|
class WorktreeArchive {
|
|
29190
29896
|
dir;
|
|
29191
29897
|
records = new Map;
|
|
29192
29898
|
constructor(dir) {
|
|
29193
29899
|
this.dir = dir;
|
|
29194
|
-
|
|
29195
|
-
for (const id of
|
|
29900
|
+
mkdirSync8(dir, { recursive: true });
|
|
29901
|
+
for (const id of readdirSync8(dir)) {
|
|
29196
29902
|
const file = this.filesOf(id).record;
|
|
29197
|
-
if (!
|
|
29903
|
+
if (!existsSync27(file))
|
|
29198
29904
|
continue;
|
|
29199
29905
|
try {
|
|
29200
29906
|
this.records.set(id, JSON.parse(readFileSync17(file, "utf8")));
|
|
@@ -29208,7 +29914,7 @@ class WorktreeArchive {
|
|
|
29208
29914
|
}
|
|
29209
29915
|
put(rec, from) {
|
|
29210
29916
|
const at = this.filesOf(rec.worktree.id);
|
|
29211
|
-
|
|
29917
|
+
mkdirSync8(at.dir, { recursive: true });
|
|
29212
29918
|
move(from.transcript, at.transcript);
|
|
29213
29919
|
move(from.attachments, at.attachments);
|
|
29214
29920
|
move(from.plans, at.plans);
|
|
@@ -29228,7 +29934,7 @@ class WorktreeArchive {
|
|
|
29228
29934
|
delete(id) {
|
|
29229
29935
|
if (!this.records.has(id))
|
|
29230
29936
|
return;
|
|
29231
|
-
|
|
29937
|
+
rmSync6(this.filesOf(id).dir, { recursive: true, force: true });
|
|
29232
29938
|
this.records.delete(id);
|
|
29233
29939
|
}
|
|
29234
29940
|
list(repo) {
|
|
@@ -29243,8 +29949,8 @@ class WorktreeArchive {
|
|
|
29243
29949
|
planFile(id, rel) {
|
|
29244
29950
|
if (!this.records.has(id) || !isPlanPath(rel))
|
|
29245
29951
|
return null;
|
|
29246
|
-
const file =
|
|
29247
|
-
if (!
|
|
29952
|
+
const file = join25(this.filesOf(id).plans, basename8(rel));
|
|
29953
|
+
if (!existsSync27(file))
|
|
29248
29954
|
return null;
|
|
29249
29955
|
try {
|
|
29250
29956
|
return readFileSync17(file, "utf8");
|
|
@@ -29254,13 +29960,13 @@ class WorktreeArchive {
|
|
|
29254
29960
|
}
|
|
29255
29961
|
}
|
|
29256
29962
|
filesOf(id) {
|
|
29257
|
-
const dir =
|
|
29963
|
+
const dir = join25(this.dir, id);
|
|
29258
29964
|
return {
|
|
29259
29965
|
dir,
|
|
29260
|
-
record:
|
|
29261
|
-
transcript:
|
|
29262
|
-
attachments:
|
|
29263
|
-
plans:
|
|
29966
|
+
record: join25(dir, "record.json"),
|
|
29967
|
+
transcript: join25(dir, "transcript.jsonl"),
|
|
29968
|
+
attachments: join25(dir, "attachments"),
|
|
29969
|
+
plans: join25(dir, "plans")
|
|
29264
29970
|
};
|
|
29265
29971
|
}
|
|
29266
29972
|
}
|
|
@@ -29285,7 +29991,7 @@ function summarize(r, repoId, transcript) {
|
|
|
29285
29991
|
};
|
|
29286
29992
|
}
|
|
29287
29993
|
function lastUsage(transcript) {
|
|
29288
|
-
if (!
|
|
29994
|
+
if (!existsSync27(transcript))
|
|
29289
29995
|
return null;
|
|
29290
29996
|
const lines = readFileSync17(transcript, "utf8").split(`
|
|
29291
29997
|
`);
|
|
@@ -29301,7 +30007,7 @@ function lastUsage(transcript) {
|
|
|
29301
30007
|
return null;
|
|
29302
30008
|
}
|
|
29303
30009
|
function firstPrompt(transcript) {
|
|
29304
|
-
if (!
|
|
30010
|
+
if (!existsSync27(transcript))
|
|
29305
30011
|
return;
|
|
29306
30012
|
for (const line of readFileSync17(transcript, "utf8").split(`
|
|
29307
30013
|
`)) {
|
|
@@ -29316,10 +30022,10 @@ function firstPrompt(transcript) {
|
|
|
29316
30022
|
return;
|
|
29317
30023
|
}
|
|
29318
30024
|
function move(from, to) {
|
|
29319
|
-
if (!
|
|
30025
|
+
if (!existsSync27(from))
|
|
29320
30026
|
return;
|
|
29321
|
-
|
|
29322
|
-
|
|
30027
|
+
mkdirSync8(dirname10(to), { recursive: true });
|
|
30028
|
+
rmSync6(to, { recursive: true, force: true });
|
|
29323
30029
|
renameSync4(from, to);
|
|
29324
30030
|
}
|
|
29325
30031
|
|
|
@@ -29381,8 +30087,8 @@ class ArchivedGit {
|
|
|
29381
30087
|
}
|
|
29382
30088
|
|
|
29383
30089
|
// ../daemon/src/worktrees/spare.ts
|
|
29384
|
-
import { existsSync as
|
|
29385
|
-
import { basename as basename9, join as
|
|
30090
|
+
import { existsSync as existsSync28 } from "fs";
|
|
30091
|
+
import { basename as basename9, join as join26 } from "path";
|
|
29386
30092
|
class SparePool {
|
|
29387
30093
|
d;
|
|
29388
30094
|
inflight = new Map;
|
|
@@ -29401,7 +30107,7 @@ class SparePool {
|
|
|
29401
30107
|
if (!text.trim())
|
|
29402
30108
|
return;
|
|
29403
30109
|
const wt = this.d.state.worktree(boxId);
|
|
29404
|
-
if (wt?.kind !== "spare" || !
|
|
30110
|
+
if (wt?.kind !== "spare" || !existsSync28(wt.path))
|
|
29405
30111
|
return;
|
|
29406
30112
|
const agent = this.d.runtime.ensureAgent(wt).agent;
|
|
29407
30113
|
if (agent.runningAgent === null)
|
|
@@ -29446,11 +30152,11 @@ class SparePool {
|
|
|
29446
30152
|
return this.record(repoId, repo).then((wt) => this.warmUp(wt, repo, {}));
|
|
29447
30153
|
}
|
|
29448
30154
|
async record(repoId, repo) {
|
|
29449
|
-
const { id, dir } = freeSlot(
|
|
30155
|
+
const { id, dir } = freeSlot(join26(this.d.paths.worktreesDir, repo.name));
|
|
29450
30156
|
const wt = {
|
|
29451
30157
|
id,
|
|
29452
30158
|
repoId,
|
|
29453
|
-
path:
|
|
30159
|
+
path: join26(this.d.paths.worktreesDir, repo.name, dir),
|
|
29454
30160
|
branch: repo.defaultBranch,
|
|
29455
30161
|
kind: "spare",
|
|
29456
30162
|
phase: "reserved",
|
|
@@ -29465,7 +30171,7 @@ class SparePool {
|
|
|
29465
30171
|
const repoId = repo.id;
|
|
29466
30172
|
let cut = () => {};
|
|
29467
30173
|
const added = (async () => {
|
|
29468
|
-
if (
|
|
30174
|
+
if (existsSync28(wt.path))
|
|
29469
30175
|
return;
|
|
29470
30176
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", wt.path, baseOf(repo)));
|
|
29471
30177
|
})();
|
|
@@ -29513,7 +30219,7 @@ class SparePool {
|
|
|
29513
30219
|
}
|
|
29514
30220
|
log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
|
|
29515
30221
|
this.d.carryDraft?.(wt.id, wt.repoId);
|
|
29516
|
-
if (
|
|
30222
|
+
if (existsSync28(wt.path)) {
|
|
29517
30223
|
await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
|
|
29518
30224
|
return;
|
|
29519
30225
|
}
|
|
@@ -29570,7 +30276,7 @@ class SparePool {
|
|
|
29570
30276
|
const running = this.inflight.get(repoId);
|
|
29571
30277
|
await running?.added.catch(() => {});
|
|
29572
30278
|
running?.cut();
|
|
29573
|
-
if (wt.kind !== "spare" || !
|
|
30279
|
+
if (wt.kind !== "spare" || !existsSync28(wt.path))
|
|
29574
30280
|
return null;
|
|
29575
30281
|
}
|
|
29576
30282
|
await this.refreshing.get(repoId);
|
|
@@ -29616,7 +30322,7 @@ class Trunk {
|
|
|
29616
30322
|
const last = this.lastFetch.get(path) ?? 0;
|
|
29617
30323
|
if (Date.now() - last > FETCH_EVERY_MS) {
|
|
29618
30324
|
this.lastFetch.set(path, Date.now());
|
|
29619
|
-
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) => {
|
|
29620
30326
|
const main = this.d.state.worktree(mainId);
|
|
29621
30327
|
if (main)
|
|
29622
30328
|
this.noteFetch(main.repoId, r);
|
|
@@ -29641,7 +30347,7 @@ class Trunk {
|
|
|
29641
30347
|
if (Date.now() - last < OPEN_FETCH_MIN_MS)
|
|
29642
30348
|
return;
|
|
29643
30349
|
this.lastFetch.set(repo.path, Date.now());
|
|
29644
|
-
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));
|
|
29645
30351
|
this.noteFetch(repo.id, f);
|
|
29646
30352
|
if (!f.ok) {
|
|
29647
30353
|
log.warn("fetch", `could not fetch ${repo.path}: ${f.err.slice(0, 200)}`);
|
|
@@ -29665,7 +30371,7 @@ class Trunk {
|
|
|
29665
30371
|
return { ok: false, message: "no main checkout to pull" };
|
|
29666
30372
|
this.lastFetch.set(repo.path, Date.now());
|
|
29667
30373
|
w.step(`pulling ${repo.defaultBranch} from origin`);
|
|
29668
|
-
const f = await w.git(repo.path, ["fetch", "--quiet"]);
|
|
30374
|
+
const f = await withFetchLock(repo.path, () => w.git(repo.path, ["fetch", "--quiet"]));
|
|
29669
30375
|
this.noteFetch(repo.id, f);
|
|
29670
30376
|
if (!f.ok)
|
|
29671
30377
|
return { ok: false, message: refused("fetch failed", f) };
|
|
@@ -29867,10 +30573,9 @@ class WorktreeService {
|
|
|
29867
30573
|
await this.discardWorktree(id);
|
|
29868
30574
|
},
|
|
29869
30575
|
carryDraft: (fromId, repoId) => {
|
|
29870
|
-
const text = this.d.drafts?.text(fromId);
|
|
29871
30576
|
const main = this.mainOf(repoId);
|
|
29872
|
-
if (
|
|
29873
|
-
this.d.drafts?.
|
|
30577
|
+
if (main)
|
|
30578
|
+
this.d.drafts?.move(fromId, main.id, true);
|
|
29874
30579
|
},
|
|
29875
30580
|
ready: (repoId) => {
|
|
29876
30581
|
const main = this.mainOf(repoId);
|
|
@@ -29879,15 +30584,15 @@ class WorktreeService {
|
|
|
29879
30584
|
if (this.d.runtime.get(main.id)?.procs) {
|
|
29880
30585
|
fireAndForget(main.id, this.d.runtime.stopPage(main.id), "main's page stops for the spare");
|
|
29881
30586
|
}
|
|
29882
|
-
const text = this.d.drafts?.text(main.id);
|
|
29883
30587
|
const spare = this.leadSpareOf(repoId);
|
|
29884
|
-
if (
|
|
29885
|
-
this.d.drafts?.
|
|
29886
|
-
this.d.drafts?.drop(main.id);
|
|
29887
|
-
}
|
|
30588
|
+
if (spare)
|
|
30589
|
+
this.d.drafts?.move(main.id, spare);
|
|
29888
30590
|
}
|
|
29889
30591
|
});
|
|
29890
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
|
+
}
|
|
29891
30596
|
if (event.type !== "usage")
|
|
29892
30597
|
return;
|
|
29893
30598
|
const { used, size, cost } = event;
|
|
@@ -29952,14 +30657,14 @@ class WorktreeService {
|
|
|
29952
30657
|
rt.agent.send(agentPrompt, { context: withCarry(context, carried), attachments });
|
|
29953
30658
|
this.scheduleNaming(claimed, task, variant);
|
|
29954
30659
|
if (carried.unmoved)
|
|
29955
|
-
throw new UserError(carried.unmoved);
|
|
30660
|
+
throw new UserError(carried.unmoved, { delivered: true });
|
|
29956
30661
|
return claimed;
|
|
29957
30662
|
}
|
|
29958
30663
|
if (!await isGitRepo(repo.path)) {
|
|
29959
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.`);
|
|
29960
30665
|
}
|
|
29961
|
-
const { id, dir } = freeSlot(
|
|
29962
|
-
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);
|
|
29963
30668
|
const branch = `toyon/${dir}`;
|
|
29964
30669
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
|
|
29965
30670
|
const wt = {
|
|
@@ -29986,7 +30691,7 @@ class WorktreeService {
|
|
|
29986
30691
|
this.d.runtime.ensureAgent(wt).agent.send(agentPrompt, { context: withCarry(context, carried), attachments });
|
|
29987
30692
|
this.scheduleNaming(wt, task, variant);
|
|
29988
30693
|
if (carried.unmoved)
|
|
29989
|
-
throw new UserError(carried.unmoved);
|
|
30694
|
+
throw new UserError(carried.unmoved, { delivered: true });
|
|
29990
30695
|
return wt;
|
|
29991
30696
|
}
|
|
29992
30697
|
async carryMain(repo, wt, move) {
|
|
@@ -30025,7 +30730,7 @@ class WorktreeService {
|
|
|
30025
30730
|
};
|
|
30026
30731
|
const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
|
|
30027
30732
|
const restored = (carried.ok ? carried.out.split(`
|
|
30028
|
-
`).filter(Boolean) : []).filter((p) => !
|
|
30733
|
+
`).filter(Boolean) : []).filter((p) => !existsSync29(join27(wt.path, p)));
|
|
30029
30734
|
const applied = await git(wt.path, "stash", "apply", sha);
|
|
30030
30735
|
if (applied.ok) {
|
|
30031
30736
|
const ref = await entry();
|
|
@@ -30061,9 +30766,9 @@ class WorktreeService {
|
|
|
30061
30766
|
const branch = kind === "pr" ? `pr/${number}` : ref;
|
|
30062
30767
|
const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
|
|
30063
30768
|
let slug = branchSlug(title) || "branch";
|
|
30064
|
-
if (
|
|
30769
|
+
if (existsSync29(join27(this.d.paths.worktreesDir, repo.name, slug)))
|
|
30065
30770
|
slug = `${slug}-${shortId().slice(0, 4)}`;
|
|
30066
|
-
const wtPath =
|
|
30771
|
+
const wtPath = join27(this.d.paths.worktreesDir, repo.name, slug);
|
|
30067
30772
|
await withRepoLock(repo.path, async () => {
|
|
30068
30773
|
const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
|
|
30069
30774
|
if (out)
|
|
@@ -30283,7 +30988,7 @@ class WorktreeService {
|
|
|
30283
30988
|
try {
|
|
30284
30989
|
await this.removeDirectory(repo, wt);
|
|
30285
30990
|
} catch (e) {
|
|
30286
|
-
|
|
30991
|
+
rmSync7(this.plansScratch(wt.id), { recursive: true, force: true });
|
|
30287
30992
|
throw e;
|
|
30288
30993
|
}
|
|
30289
30994
|
if (hasOwnBranch(wt)) {
|
|
@@ -30297,6 +31002,7 @@ class WorktreeService {
|
|
|
30297
31002
|
this.d.state.removeWorktree(worktreeId);
|
|
30298
31003
|
releasePort(wt.proxyPort);
|
|
30299
31004
|
let archived = null;
|
|
31005
|
+
await dropTurnRefs(repo.path, worktreeId);
|
|
30300
31006
|
if (archive)
|
|
30301
31007
|
archived = this.archiveChat(wt, repo, kept, sessionId, reason);
|
|
30302
31008
|
else {
|
|
@@ -30309,7 +31015,7 @@ class WorktreeService {
|
|
|
30309
31015
|
}
|
|
30310
31016
|
async teardown(wt, repo) {
|
|
30311
31017
|
const cmds = repo.config.teardown ?? [];
|
|
30312
|
-
if (cmds.length === 0 || !
|
|
31018
|
+
if (cmds.length === 0 || !existsSync29(wt.path))
|
|
30313
31019
|
return;
|
|
30314
31020
|
for (const cmd of cmds) {
|
|
30315
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") });
|
|
@@ -30318,7 +31024,7 @@ class WorktreeService {
|
|
|
30318
31024
|
}
|
|
30319
31025
|
}
|
|
30320
31026
|
async keep(repo, wt) {
|
|
30321
|
-
const index =
|
|
31027
|
+
const index = join27(this.d.paths.archiveDir, `${wt.id}.index`);
|
|
30322
31028
|
const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
|
|
30323
31029
|
if (!kept)
|
|
30324
31030
|
log.warn(wt.id, "could not keep its git state");
|
|
@@ -30355,11 +31061,11 @@ class WorktreeService {
|
|
|
30355
31061
|
log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
|
|
30356
31062
|
}
|
|
30357
31063
|
plansScratch(worktreeId) {
|
|
30358
|
-
return
|
|
31064
|
+
return join27(this.d.paths.archiveDir, `${worktreeId}.plans`);
|
|
30359
31065
|
}
|
|
30360
31066
|
keepPlans(wt) {
|
|
30361
|
-
const from =
|
|
30362
|
-
if (!
|
|
31067
|
+
const from = join27(wt.path, PLANS_DIR);
|
|
31068
|
+
if (!existsSync29(from))
|
|
30363
31069
|
return;
|
|
30364
31070
|
try {
|
|
30365
31071
|
cpSync(from, this.plansScratch(wt.id), { recursive: true });
|
|
@@ -30400,8 +31106,8 @@ class WorktreeService {
|
|
|
30400
31106
|
deleteChat(worktreeId) {
|
|
30401
31107
|
const files = this.chatFiles(worktreeId);
|
|
30402
31108
|
try {
|
|
30403
|
-
|
|
30404
|
-
|
|
31109
|
+
rmSync7(files.transcript, { force: true });
|
|
31110
|
+
rmSync7(files.attachments, { recursive: true, force: true });
|
|
30405
31111
|
} catch (e) {
|
|
30406
31112
|
log.warn(worktreeId, "could not delete transcript or attachments", e);
|
|
30407
31113
|
}
|
|
@@ -30447,7 +31153,7 @@ class WorktreeService {
|
|
|
30447
31153
|
}
|
|
30448
31154
|
archivedAttachment(archiveId, file) {
|
|
30449
31155
|
const files = this.archive.chatFiles(archiveId);
|
|
30450
|
-
return files && isAttachmentFile(file) ?
|
|
31156
|
+
return files && isAttachmentFile(file) ? join27(files.attachments, file) : null;
|
|
30451
31157
|
}
|
|
30452
31158
|
archivedGit(archiveId) {
|
|
30453
31159
|
const rec = this.archive.get(archiveId);
|
|
@@ -30490,7 +31196,7 @@ class WorktreeService {
|
|
|
30490
31196
|
throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
|
|
30491
31197
|
if (rec.auto)
|
|
30492
31198
|
log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
|
|
30493
|
-
const path =
|
|
31199
|
+
const path = existsSync29(old.path) ? join27(dirname11(old.path), freeSlot(dirname11(old.path)).dir) : old.path;
|
|
30494
31200
|
const branch = await withRepoLock(repo.path, async () => {
|
|
30495
31201
|
if (!await commitOf(repo.path, archiveRef(old.id))) {
|
|
30496
31202
|
throw new UserError(`${old.title}'s kept commits are gone from git`);
|
|
@@ -30524,10 +31230,11 @@ class WorktreeService {
|
|
|
30524
31230
|
branch,
|
|
30525
31231
|
proxyPort: await allocateProxyPort(),
|
|
30526
31232
|
promptedAt: Date.now(),
|
|
31233
|
+
parked: true,
|
|
30527
31234
|
...createdBy ? { createdBy } : {}
|
|
30528
31235
|
};
|
|
30529
31236
|
try {
|
|
30530
|
-
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) });
|
|
30531
31238
|
} catch (e) {
|
|
30532
31239
|
log.warn(old.id, "could not move its chat back out of the archive", e);
|
|
30533
31240
|
}
|
|
@@ -30556,12 +31263,12 @@ class WorktreeService {
|
|
|
30556
31263
|
return;
|
|
30557
31264
|
const repo = this.repoOf(rec);
|
|
30558
31265
|
const repoPath = repo?.path ?? rec.repoPath;
|
|
30559
|
-
if (rec.kept &&
|
|
31266
|
+
if (rec.kept && existsSync29(repoPath)) {
|
|
30560
31267
|
const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
|
|
30561
31268
|
if (!r.ok)
|
|
30562
31269
|
log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
|
|
30563
31270
|
}
|
|
30564
|
-
if (rec.worktree.lands?.length &&
|
|
31271
|
+
if (rec.worktree.lands?.length && existsSync29(repoPath))
|
|
30565
31272
|
await dropLandRefs(repoPath, archiveId);
|
|
30566
31273
|
this.archive.delete(archiveId);
|
|
30567
31274
|
this.d.drafts?.drop(archiveId);
|
|
@@ -30652,6 +31359,15 @@ class WorktreeService {
|
|
|
30652
31359
|
this.d.hub.emit("worktreesChanged");
|
|
30653
31360
|
return { target, grafted: sources.map((w) => w.title) };
|
|
30654
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
|
+
}
|
|
30655
31371
|
async setupAndStart(wt, repo, depsSource = repo.path, opts = {}) {
|
|
30656
31372
|
this.d.runtime.markSetup(wt.id, true);
|
|
30657
31373
|
try {
|
|
@@ -30661,9 +31377,9 @@ class WorktreeService {
|
|
|
30661
31377
|
}
|
|
30662
31378
|
}
|
|
30663
31379
|
async mirrorBaseIgnore(repo) {
|
|
30664
|
-
const src =
|
|
30665
|
-
const tracked =
|
|
30666
|
-
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;
|
|
30667
31383
|
const lines = mirror ? readFileSync18(src, "utf8").split(`
|
|
30668
31384
|
`).filter((l) => l.trim() !== "") : [];
|
|
30669
31385
|
await excludeBlock(repo.path, BASE_IGNORE, lines);
|
|
@@ -30676,9 +31392,9 @@ class WorktreeService {
|
|
|
30676
31392
|
await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
|
|
30677
31393
|
run.finish(0);
|
|
30678
31394
|
}
|
|
30679
|
-
const srcNm =
|
|
30680
|
-
const dstNm =
|
|
30681
|
-
if (
|
|
31395
|
+
const srcNm = join27(depsSource, "node_modules");
|
|
31396
|
+
const dstNm = join27(wt.path, "node_modules");
|
|
31397
|
+
if (existsSync29(srcNm) && !existsSync29(dstNm)) {
|
|
30682
31398
|
const r = await cloneTree(srcNm, dstNm);
|
|
30683
31399
|
if (r.ok)
|
|
30684
31400
|
this.d.hub.emit("log", wt.id, "setup", `deps via ${r.how} in ${r.ms}ms`);
|
|
@@ -30686,10 +31402,10 @@ class WorktreeService {
|
|
|
30686
31402
|
log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
|
|
30687
31403
|
}
|
|
30688
31404
|
for (const f of LOCAL_CONFIG_FILES) {
|
|
30689
|
-
const src =
|
|
30690
|
-
if (!
|
|
31405
|
+
const src = join27(depsSource, f);
|
|
31406
|
+
if (!existsSync29(src) || existsSync29(join27(wt.path, f)))
|
|
30691
31407
|
continue;
|
|
30692
|
-
const r = await run("cp", [src,
|
|
31408
|
+
const r = await run("cp", [src, join27(wt.path, f)], wt.path);
|
|
30693
31409
|
if (r.ok)
|
|
30694
31410
|
this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
|
|
30695
31411
|
else
|
|
@@ -31097,7 +31813,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31097
31813
|
async fetchBase(wt, repo, w) {
|
|
31098
31814
|
if (!baseIsRemote(repo))
|
|
31099
31815
|
return { ok: true, message: `${baseOf(repo)} here is the base` };
|
|
31100
|
-
const r = await fetchBase(wt.path, repo, w);
|
|
31816
|
+
const r = await withFetchLock(repo.path, () => fetchBase(wt.path, repo, w));
|
|
31101
31817
|
this.trunk.noteFetch(repo.id, { ok: r.ok, err: r.message });
|
|
31102
31818
|
return r;
|
|
31103
31819
|
}
|
|
@@ -31333,6 +32049,11 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31333
32049
|
base: baseOf(repo)
|
|
31334
32050
|
};
|
|
31335
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
|
+
}
|
|
31336
32057
|
async gitStatus(worktreeId) {
|
|
31337
32058
|
if (this.going.has(worktreeId))
|
|
31338
32059
|
return null;
|
|
@@ -31353,6 +32074,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31353
32074
|
const counts = { ...ab, ...unpushed === undefined ? {} : { unpushed } };
|
|
31354
32075
|
const ahead = counts.ahead ?? 0;
|
|
31355
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;
|
|
31356
32078
|
if (r.wt && !isMain && hasOwnBranch(r.wt) && files.length === 0 && ahead === 0 && head.ok && this.handChecked.get(r.wt.id) !== head.out) {
|
|
31357
32079
|
this.handChecked.set(r.wt.id, head.out);
|
|
31358
32080
|
const mark = await handLanding(r.path, r.wt.branch, r.base, r.wt.lands?.at(-1)?.tip);
|
|
@@ -31379,7 +32101,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31379
32101
|
if (this.going.has(worktreeId))
|
|
31380
32102
|
return null;
|
|
31381
32103
|
this.noteCounts(worktreeId, isMain, files.length, counts);
|
|
31382
|
-
return { files, committed, head: head.ok ? head.out : undefined, ...counts };
|
|
32104
|
+
return { files, committed, ...turn ? { turn } : {}, head: head.ok ? head.out : undefined, ...counts };
|
|
31383
32105
|
} catch (e) {
|
|
31384
32106
|
log.warn(worktreeId, "git status failed", e);
|
|
31385
32107
|
return null;
|
|
@@ -31648,7 +32370,8 @@ var accounts = new AgentAccounts({
|
|
|
31648
32370
|
connect: async (app, spec) => spawnAcp(app, agents.launch(spec, await prepareLaunch(paths.scratchDir, spec)), paths.scratchDir, `auth:${spec.id}`)
|
|
31649
32371
|
});
|
|
31650
32372
|
accounts.onChange = () => hub.emit("agentsChanged");
|
|
31651
|
-
var
|
|
32373
|
+
var uploads = new UploadStore(paths.uploadsDir);
|
|
32374
|
+
var attachments2 = new AttachmentStore(paths.attachmentsDir, uploads);
|
|
31652
32375
|
var runtime = new RuntimeRegistry({
|
|
31653
32376
|
hub,
|
|
31654
32377
|
state,
|
|
@@ -31658,13 +32381,19 @@ var runtime = new RuntimeRegistry({
|
|
|
31658
32381
|
accounts,
|
|
31659
32382
|
attachments: attachments2,
|
|
31660
32383
|
bridgeScript: () => bridge.get(),
|
|
32384
|
+
turnStarting: (id) => worktrees.turnStarted(id),
|
|
31661
32385
|
remote,
|
|
31662
32386
|
grant: previewGrant(token),
|
|
31663
32387
|
viewed: (id) => idle.isViewed(id),
|
|
31664
32388
|
shown: (id) => idle.isShown(id),
|
|
31665
32389
|
mainLeads: (repoId) => worktrees.spare.current(repoId) === null
|
|
31666
32390
|
});
|
|
31667
|
-
var drafts = new DraftStore({
|
|
32391
|
+
var drafts = new DraftStore({
|
|
32392
|
+
file: paths.draftsFile,
|
|
32393
|
+
attachmentsFile: paths.draftAttachmentsFile,
|
|
32394
|
+
hub,
|
|
32395
|
+
uploads
|
|
32396
|
+
});
|
|
31668
32397
|
var runs = new RunService({ state, hub });
|
|
31669
32398
|
var exec = new ExecService({ state, runtime, shipping: (id) => worktrees.shippingOf(id) });
|
|
31670
32399
|
var cache = new ArtifactCache({ paths, state, hub });
|
|
@@ -31834,6 +32563,7 @@ var stopLagSampler = startLagSampler();
|
|
|
31834
32563
|
await runtime.reclaimOrphans();
|
|
31835
32564
|
await repos.boot();
|
|
31836
32565
|
drafts.prune((id) => !!state.worktree(id) || worktrees.hasArchived(id));
|
|
32566
|
+
uploads.sweep(drafts.uploadIds());
|
|
31837
32567
|
idle.boot();
|
|
31838
32568
|
runs.boot();
|
|
31839
32569
|
landing.boot();
|
|
@@ -31889,7 +32619,7 @@ async function shutdown(signal, opts = {}) {
|
|
|
31889
32619
|
const deadline = new Promise((resolve) => setTimeout(resolve, 5000));
|
|
31890
32620
|
await Promise.race([Promise.all([runtime.shutdown(), exec.stopAll()]), deadline]);
|
|
31891
32621
|
state.flushGroups();
|
|
31892
|
-
|
|
32622
|
+
rmSync8(paths.pidFile, { force: true });
|
|
31893
32623
|
if (opts.respawn) {
|
|
31894
32624
|
await Bun.sleep(250);
|
|
31895
32625
|
log.info("daemon", "starting the replacement");
|