toyon 0.8.1 → 0.10.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 +68 -15
- package/dist/daemon.js +1882 -561
- package/dist/shell/assets/{Editor-CrfFlcLA.js → Editor-DL0q1Bu5.js} +170 -170
- package/dist/shell/assets/{XTerm-CsuyFSMK.js → XTerm-C9YH4Lh2.js} +16 -16
- package/dist/shell/assets/{cssMode-CWkICf31.js → cssMode-Et22hXpa.js} +1 -1
- package/dist/shell/assets/{freemarker2-C2fP12sE.js → freemarker2-Clz20PcM.js} +1 -1
- package/dist/shell/assets/{grammar-5S_Kl1e8.js → grammar-CPvdB3az.js} +5 -5
- package/dist/shell/assets/{handlebars-CkRyqZGC.js → handlebars-B4nvXAx8.js} +1 -1
- package/dist/shell/assets/{html-C9iJKWKA.js → html-DPIGgBLK.js} +1 -1
- package/dist/shell/assets/{htmlMode-Dps-6NJd.js → htmlMode-C2LaVuEX.js} +1 -1
- package/dist/shell/assets/index-2QusTgw-.js +262 -0
- package/dist/shell/assets/index-TaJLoLSd.css +1 -0
- package/dist/shell/assets/{javascript-DM5EiBY7.js → javascript-CnwJfTbN.js} +1 -1
- package/dist/shell/assets/{jsonMode-ampRCTsW.js → jsonMode-8znCTiqR.js} +1 -1
- package/dist/shell/assets/{liquid-CEUat1ZS.js → liquid-Bdm-1CUh.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-ChupARNW.js → lspLanguageFeatures-B1DMU7yH.js} +1 -1
- package/dist/shell/assets/{mdx-DAglTq4w.js → mdx-Ktcqo9Jf.js} +1 -1
- package/dist/shell/assets/{python-DOPtAG-1.js → python-QX--d8LR.js} +1 -1
- package/dist/shell/assets/{razor-qRDOBjWc.js → razor-C0LrXteG.js} +1 -1
- package/dist/shell/assets/{tsMode-BD49NPBt.js → tsMode-BtYCp6db.js} +1 -1
- package/dist/shell/assets/{xml-p5Js1H7t.js → xml-BXbaQ9MG.js} +1 -1
- package/dist/shell/assets/{yaml-CctH9qfh.js → yaml-C6FhaU_C.js} +1 -1
- package/dist/shell/index.html +26 -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
|
|
|
@@ -58,6 +58,7 @@ function applyLog(lines, proc, line, retract = 0) {
|
|
|
58
58
|
return out;
|
|
59
59
|
return [...out, { proc, line }];
|
|
60
60
|
}
|
|
61
|
+
var KEEP_AWAKE_MODES = ["use", "always", "off"];
|
|
61
62
|
|
|
62
63
|
// ../shared/src/worktree-caps.ts
|
|
63
64
|
function isMain(wt) {
|
|
@@ -119,9 +120,10 @@ function archiveReason(wt, f) {
|
|
|
119
120
|
return null;
|
|
120
121
|
if (unitsAhead(wt, f.rows) < ARCHIVE_KEEP_RECENT)
|
|
121
122
|
return null;
|
|
122
|
-
const what = lost ? "
|
|
123
|
-
return `${what},
|
|
123
|
+
const what = lost ? "sibling landed" : wt.landed ? "landed" : "no changes";
|
|
124
|
+
return `${what}, ${UNOPENED}${duration(f.afterMs)}`;
|
|
124
125
|
}
|
|
126
|
+
var UNOPENED = "not opened in ";
|
|
125
127
|
function nothingToKeep(w, f, lost) {
|
|
126
128
|
if (w.kind !== "worktree" || !hasOwnBranch(w))
|
|
127
129
|
return false;
|
|
@@ -157,22 +159,30 @@ function duration(ms) {
|
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
// ../shared/src/attachment.ts
|
|
160
|
-
var ATTACHMENT_KINDS = ["image", "paste", "pick"];
|
|
161
|
-
var ATTACHMENT_LIMITS = { image: 6, paste: 4, pick: 8 };
|
|
162
|
+
var ATTACHMENT_KINDS = ["image", "paste", "file", "pick"];
|
|
163
|
+
var ATTACHMENT_LIMITS = { image: 6, paste: 4, file: 8, pick: 8 };
|
|
162
164
|
var ATTACHMENTS_PER_MESSAGE = ATTACHMENT_KINDS.reduce((sum, k) => sum + ATTACHMENT_LIMITS[k], 0);
|
|
163
|
-
var KIND_NOUN = {
|
|
165
|
+
var KIND_NOUN = {
|
|
166
|
+
image: "image",
|
|
167
|
+
paste: "paste",
|
|
168
|
+
file: "file",
|
|
169
|
+
pick: "element"
|
|
170
|
+
};
|
|
164
171
|
var KIND_LABEL = { image: "Image", paste: "Pasted text" };
|
|
165
172
|
var attachmentLabel = (kind, n) => `${KIND_LABEL[kind]} ${n}`;
|
|
166
173
|
var limitMessage = (kind) => `at most ${ATTACHMENT_LIMITS[kind]} ${KIND_NOUN[kind]}s per message`;
|
|
167
174
|
var countOf = (list, kind) => list.filter((a) => a.kind === kind).length;
|
|
168
175
|
var overLimit = (list) => ATTACHMENT_KINDS.find((k) => countOf(list, k) > ATTACHMENT_LIMITS[k]) ?? null;
|
|
169
176
|
function nextNumbers(sent) {
|
|
170
|
-
const next = { image: 1, paste: 1, pick: 1 };
|
|
177
|
+
const next = { image: 1, paste: 1, file: 1, pick: 1 };
|
|
171
178
|
for (const list of sent)
|
|
172
179
|
for (const a of list ?? [])
|
|
173
180
|
next[a.kind] = Math.max(next[a.kind], a.n + 1);
|
|
174
181
|
return next;
|
|
175
182
|
}
|
|
183
|
+
function fmtBytes(n) {
|
|
184
|
+
return n >= 1024 * 1024 ? `${(n / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(n / 1024))} KB`;
|
|
185
|
+
}
|
|
176
186
|
|
|
177
187
|
// ../shared/src/cache.ts
|
|
178
188
|
function cachePolicy(config) {
|
|
@@ -6464,17 +6474,33 @@ function describeManaged(p) {
|
|
|
6464
6474
|
return out;
|
|
6465
6475
|
}
|
|
6466
6476
|
|
|
6477
|
+
// ../shared/src/output.ts
|
|
6478
|
+
var CONTEXT_CHARS = 6000;
|
|
6479
|
+
var HEAD_CHARS = 1500;
|
|
6480
|
+
function clipOutput(text) {
|
|
6481
|
+
if (text.length <= CONTEXT_CHARS)
|
|
6482
|
+
return text;
|
|
6483
|
+
const cut = text.length - CONTEXT_CHARS;
|
|
6484
|
+
return `${text.slice(0, HEAD_CHARS)}
|
|
6485
|
+
[${cut} characters cut here]
|
|
6486
|
+
${text.slice(text.length - (CONTEXT_CHARS - HEAD_CHARS))}`;
|
|
6487
|
+
}
|
|
6488
|
+
|
|
6467
6489
|
// ../shared/src/pair.ts
|
|
6468
6490
|
var PAIR_TTL_MS = 120000;
|
|
6469
6491
|
var pairLink = (host, code) => `https://${host}/#pair=${code}`;
|
|
6492
|
+
var isTailnetName = (host) => host.endsWith(".ts.net");
|
|
6470
6493
|
|
|
6471
6494
|
// ../shared/src/protocol/limits.ts
|
|
6472
|
-
var PROTOCOL_VERSION =
|
|
6495
|
+
var PROTOCOL_VERSION = 89;
|
|
6473
6496
|
var FILE_MAX_CHARS = 5000000;
|
|
6474
6497
|
var IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
6475
6498
|
var IMAGE_MAX_EDGE = 2576;
|
|
6476
6499
|
var IMAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
6477
6500
|
var PASTE_MAX_CHARS = 1e5;
|
|
6501
|
+
var FILE_INLINE_CHARS = 20000;
|
|
6502
|
+
var UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
6503
|
+
var VARIANTS_MAX = 3;
|
|
6478
6504
|
|
|
6479
6505
|
// ../shared/src/paste.ts
|
|
6480
6506
|
var PREVIEW_CHARS = 80;
|
|
@@ -6559,11 +6585,13 @@ var termInput = string2().max(65536);
|
|
|
6559
6585
|
var streamName = string2().min(1).max(100);
|
|
6560
6586
|
var routePath = string2().min(1).max(2000);
|
|
6561
6587
|
var pageTitle = string2().max(1000);
|
|
6588
|
+
var uploadId = string2().regex(/^[A-Za-z0-9_-]{1,64}$/);
|
|
6562
6589
|
var imageInputSchema = object({
|
|
6563
6590
|
kind: literal("image"),
|
|
6564
6591
|
name: string2().max(200),
|
|
6565
6592
|
mimeType: _enum(IMAGE_MIME_TYPES),
|
|
6566
|
-
|
|
6593
|
+
upload: uploadId,
|
|
6594
|
+
bytes: number2().int().min(1).max(IMAGE_MAX_BYTES),
|
|
6567
6595
|
width: number2().int().min(1).max(IMAGE_MAX_EDGE),
|
|
6568
6596
|
height: number2().int().min(1).max(IMAGE_MAX_EDGE)
|
|
6569
6597
|
});
|
|
@@ -6579,6 +6607,13 @@ var pasteInputSchema = object({
|
|
|
6579
6607
|
name: string2().max(200).optional(),
|
|
6580
6608
|
source: pasteSourceSchema.optional()
|
|
6581
6609
|
});
|
|
6610
|
+
var fileInputSchema = object({
|
|
6611
|
+
kind: literal("file"),
|
|
6612
|
+
upload: uploadId,
|
|
6613
|
+
name: string2().min(1).max(200),
|
|
6614
|
+
bytes: number2().int().min(1).max(UPLOAD_MAX_BYTES),
|
|
6615
|
+
text: boolean2()
|
|
6616
|
+
});
|
|
6582
6617
|
var pickInputSchema = pickMetaSchema.extend({
|
|
6583
6618
|
kind: literal("pick"),
|
|
6584
6619
|
text: string2().max(120),
|
|
@@ -6587,13 +6622,16 @@ var pickInputSchema = pickMetaSchema.extend({
|
|
|
6587
6622
|
var attachmentInputSchema = discriminatedUnion("kind", [
|
|
6588
6623
|
imageInputSchema,
|
|
6589
6624
|
pasteInputSchema,
|
|
6625
|
+
fileInputSchema,
|
|
6590
6626
|
pickInputSchema
|
|
6591
6627
|
]);
|
|
6592
|
-
var
|
|
6628
|
+
var attachmentList = array(attachmentInputSchema).max(ATTACHMENTS_PER_MESSAGE).superRefine((list, ctx) => {
|
|
6593
6629
|
const over = overLimit(list);
|
|
6594
6630
|
if (over)
|
|
6595
6631
|
ctx.addIssue({ code: "custom", message: limitMessage(over) });
|
|
6596
|
-
})
|
|
6632
|
+
});
|
|
6633
|
+
var attachments = attachmentList.optional();
|
|
6634
|
+
var boxId = id.optional();
|
|
6597
6635
|
var askAnswerSchema = object({
|
|
6598
6636
|
selected: array(string2().max(2000)).max(32),
|
|
6599
6637
|
note: string2().max(1e4).optional()
|
|
@@ -6702,7 +6740,6 @@ var themePrefsSchema = object({
|
|
|
6702
6740
|
light: string2(),
|
|
6703
6741
|
dark: string2()
|
|
6704
6742
|
});
|
|
6705
|
-
var variantSchema = object({ group: string2(), index: number2().int().min(1), of: number2().int().min(1) });
|
|
6706
6743
|
var clientMsgSchema = discriminatedUnion("t", [
|
|
6707
6744
|
object({ t: literal("subscribe"), worktreeId: id }),
|
|
6708
6745
|
object({ t: literal("unsubscribe"), worktreeId: id }),
|
|
@@ -6713,7 +6750,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6713
6750
|
clientId: string2().max(64).optional(),
|
|
6714
6751
|
text: prose,
|
|
6715
6752
|
context: ambient.optional(),
|
|
6716
|
-
attachments
|
|
6753
|
+
attachments,
|
|
6754
|
+
boxId
|
|
6717
6755
|
}),
|
|
6718
6756
|
object({
|
|
6719
6757
|
t: literal("create-worktree"),
|
|
@@ -6721,7 +6759,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6721
6759
|
repoId: id,
|
|
6722
6760
|
worktreeId: id.optional(),
|
|
6723
6761
|
prompt,
|
|
6724
|
-
|
|
6762
|
+
variants: number2().int().min(1).max(VARIANTS_MAX).optional(),
|
|
6725
6763
|
context: ambient.optional(),
|
|
6726
6764
|
attachments,
|
|
6727
6765
|
agent: id.optional(),
|
|
@@ -6729,7 +6767,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6729
6767
|
mode: permissionModeSchema.optional(),
|
|
6730
6768
|
model: string2().max(200).optional(),
|
|
6731
6769
|
effort: string2().max(100).optional(),
|
|
6732
|
-
carry: boolean2().optional()
|
|
6770
|
+
carry: boolean2().optional(),
|
|
6771
|
+
boxId
|
|
6733
6772
|
}),
|
|
6734
6773
|
object({
|
|
6735
6774
|
t: literal("batch-worktrees"),
|
|
@@ -6737,7 +6776,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6737
6776
|
prompt,
|
|
6738
6777
|
agent: id.optional(),
|
|
6739
6778
|
model: string2().max(200).optional(),
|
|
6740
|
-
effort: string2().max(100).optional()
|
|
6779
|
+
effort: string2().max(100).optional(),
|
|
6780
|
+
boxId
|
|
6741
6781
|
}),
|
|
6742
6782
|
object({ t: literal("set-worktree-mode"), worktreeId: id, mode: permissionModeSchema }),
|
|
6743
6783
|
object({ t: literal("set-worktree-model"), worktreeId: id, model: string2().max(200) }),
|
|
@@ -6751,16 +6791,26 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6751
6791
|
t: literal("restore-worktree"),
|
|
6752
6792
|
archiveId: id,
|
|
6753
6793
|
clientId: string2().max(64).optional(),
|
|
6754
|
-
message: object({ text: prose, attachments }).optional()
|
|
6794
|
+
message: object({ text: prose, attachments }).optional(),
|
|
6795
|
+
boxId
|
|
6755
6796
|
}),
|
|
6756
6797
|
object({ t: literal("delete-archived"), archiveId: id }),
|
|
6757
6798
|
object({
|
|
6758
6799
|
t: literal("adopt-worktree"),
|
|
6759
6800
|
clientId: string2().max(64).optional(),
|
|
6760
|
-
worktreeId: id
|
|
6801
|
+
worktreeId: id,
|
|
6802
|
+
message: object({ text: prose, attachments }).optional(),
|
|
6803
|
+
boxId
|
|
6761
6804
|
}),
|
|
6762
6805
|
object({ t: literal("git-status"), worktreeId: id }),
|
|
6763
|
-
object({
|
|
6806
|
+
object({
|
|
6807
|
+
t: literal("read-file"),
|
|
6808
|
+
worktreeId: id,
|
|
6809
|
+
path: relPath,
|
|
6810
|
+
ref: sha.optional(),
|
|
6811
|
+
since: sha.optional(),
|
|
6812
|
+
seq
|
|
6813
|
+
}),
|
|
6764
6814
|
object({ t: literal("blame-file"), worktreeId: id, path: relPath, ref: sha.optional(), seq }),
|
|
6765
6815
|
object({ t: literal("git-log"), worktreeId: id }),
|
|
6766
6816
|
object({ t: literal("git-commit"), worktreeId: id, sha }),
|
|
@@ -6796,17 +6846,31 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6796
6846
|
object({ t: literal("design-scan"), worktreeId: id }),
|
|
6797
6847
|
object({ t: literal("discard-files"), worktreeId: id, paths: array(relPath).min(1).max(500) }),
|
|
6798
6848
|
object({ t: literal("judge"), worktreeId: id, note: string2().max(500).optional() }),
|
|
6849
|
+
object({ t: literal("fix-preview"), worktreeId: id, clientId: string2().max(64).optional() }),
|
|
6850
|
+
object({ t: literal("fix"), worktreeId: id, toolId: string2().min(1).max(200) }),
|
|
6799
6851
|
object({ t: literal("reveal"), worktreeId: id, path: relPath.optional() }),
|
|
6800
6852
|
object({ t: literal("stop-agent"), worktreeId: id }),
|
|
6801
6853
|
object({ t: literal("seen"), worktreeId: id }),
|
|
6802
6854
|
object({ t: literal("mark-unread"), worktreeId: id }),
|
|
6803
6855
|
object({ t: literal("set-draft"), boxId: id, text: prose, clientId: string2().max(64).optional() }),
|
|
6856
|
+
object({
|
|
6857
|
+
t: literal("set-attachments"),
|
|
6858
|
+
boxId: id,
|
|
6859
|
+
items: attachmentList,
|
|
6860
|
+
clientId: string2().max(64).optional()
|
|
6861
|
+
}),
|
|
6804
6862
|
object({ t: literal("refresh-git"), repoId: id }),
|
|
6805
6863
|
object({ t: literal("visit"), worktreeId: id, path: routePath, title: pageTitle.optional() }),
|
|
6806
6864
|
object({ t: literal("page-title"), worktreeId: id, path: routePath, title: pageTitle }),
|
|
6807
6865
|
object({ t: literal("forget-visit"), repoId: id, path: routePath }),
|
|
6808
6866
|
object({ t: literal("pick-variant"), worktreeId: id }),
|
|
6809
|
-
object({
|
|
6867
|
+
object({
|
|
6868
|
+
t: literal("unqueue"),
|
|
6869
|
+
worktreeId: id,
|
|
6870
|
+
index: number2().int().min(0),
|
|
6871
|
+
edit: boolean2().optional()
|
|
6872
|
+
}),
|
|
6873
|
+
object({ t: literal("redraft"), worktreeId: id, seq: number2().int().min(0) }),
|
|
6810
6874
|
object({ t: literal("changed-ranges"), worktreeId: id, path: relPath }),
|
|
6811
6875
|
object({ t: literal("rename-worktree"), worktreeId: id, title: string2().min(1).max(200) }),
|
|
6812
6876
|
object({
|
|
@@ -6842,6 +6906,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6842
6906
|
object({ t: literal("set-theme"), prefs: themePrefsSchema }),
|
|
6843
6907
|
object({ t: literal("zone"), tz: string2().max(100) }),
|
|
6844
6908
|
object({ t: literal("set-default-agent"), agent: id }),
|
|
6909
|
+
object({ t: literal("set-keep-awake"), mode: _enum(KEEP_AWAKE_MODES) }),
|
|
6845
6910
|
object({ t: literal("install-agent"), agent: id }),
|
|
6846
6911
|
object({
|
|
6847
6912
|
t: literal("agent-auth"),
|
|
@@ -6870,6 +6935,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6870
6935
|
object({ t: literal("exec"), worktreeId: id, command: shellCommand.min(1) }),
|
|
6871
6936
|
object({ t: literal("exec-stop"), worktreeId: id, toolId: id }),
|
|
6872
6937
|
object({ t: literal("run-stop"), worktreeId: id, kind: _enum(["setup", "check", "commit"]) }),
|
|
6938
|
+
object({ t: literal("preview-start"), worktreeId: id }),
|
|
6873
6939
|
object({ t: literal("search-refs"), repoId: id, query: string2().max(200) }),
|
|
6874
6940
|
object({ t: literal("search-chats"), repoId: id, query: string2().max(200) }),
|
|
6875
6941
|
object({
|
|
@@ -8154,7 +8220,7 @@ function safeUser() {
|
|
|
8154
8220
|
// ../daemon/package.json
|
|
8155
8221
|
var package_default = {
|
|
8156
8222
|
name: "@toyon/daemon",
|
|
8157
|
-
version: "0.
|
|
8223
|
+
version: "0.10.0",
|
|
8158
8224
|
private: true,
|
|
8159
8225
|
type: "module",
|
|
8160
8226
|
main: "src/index.ts",
|
|
@@ -12248,9 +12314,11 @@ var legacyClientNotificationMethods = new Set([
|
|
|
12248
12314
|
|
|
12249
12315
|
// ../daemon/src/core/errors.ts
|
|
12250
12316
|
class UserError extends Error {
|
|
12251
|
-
|
|
12317
|
+
delivered;
|
|
12318
|
+
constructor(message, about = {}) {
|
|
12252
12319
|
super(message);
|
|
12253
12320
|
this.name = "UserError";
|
|
12321
|
+
this.delivered = about.delivered ?? false;
|
|
12254
12322
|
}
|
|
12255
12323
|
}
|
|
12256
12324
|
|
|
@@ -12503,18 +12571,177 @@ function spawnAcp(app, launch, cwd, tag) {
|
|
|
12503
12571
|
|
|
12504
12572
|
// ../daemon/src/agent/attachments.ts
|
|
12505
12573
|
import { createHash } from "crypto";
|
|
12506
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
12574
|
+
import { copyFile as copyFile2, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
12575
|
+
import { join as join2 } from "path";
|
|
12576
|
+
|
|
12577
|
+
// ../daemon/src/agent/uploads.ts
|
|
12578
|
+
import { randomBytes } from "crypto";
|
|
12579
|
+
import { mkdirSync, readdirSync, rmSync } from "fs";
|
|
12580
|
+
import { copyFile } from "fs/promises";
|
|
12507
12581
|
import { join } from "path";
|
|
12508
|
-
var
|
|
12582
|
+
var IMAGE_EXT = {
|
|
12509
12583
|
"image/png": "png",
|
|
12510
12584
|
"image/jpeg": "jpg",
|
|
12511
12585
|
"image/gif": "gif",
|
|
12512
12586
|
"image/webp": "webp"
|
|
12513
12587
|
};
|
|
12588
|
+
var MIME_OF_EXT = new Map(IMAGE_MIME_TYPES.map((mime) => [IMAGE_EXT[mime], mime]));
|
|
12589
|
+
var OTHER_EXT = "bin";
|
|
12590
|
+
var NAME = /^([A-Za-z0-9_-]{1,64})\.(png|jpg|gif|webp|bin)$/;
|
|
12591
|
+
var UNNAMED_MS = 10 * 60000;
|
|
12592
|
+
var GONE = "an attachment is no longer on this machine; attach it again";
|
|
12593
|
+
var isUploadKind = (kind) => kind === "image" || kind === "file";
|
|
12594
|
+
function uploadIds(items) {
|
|
12595
|
+
return (items ?? []).flatMap((a) => a.kind === "image" || a.kind === "file" ? [a.upload] : []);
|
|
12596
|
+
}
|
|
12597
|
+
|
|
12598
|
+
class UploadStore {
|
|
12599
|
+
dir;
|
|
12600
|
+
unnamedMs;
|
|
12601
|
+
files = new Map;
|
|
12602
|
+
holds = new Map;
|
|
12603
|
+
named = new Set;
|
|
12604
|
+
constructor(dir, unnamedMs = UNNAMED_MS) {
|
|
12605
|
+
this.dir = dir;
|
|
12606
|
+
this.unnamedMs = unnamedMs;
|
|
12607
|
+
mkdirSync(dir, { recursive: true });
|
|
12608
|
+
for (const file of readdirSync(dir)) {
|
|
12609
|
+
const id = NAME.exec(file)?.[1];
|
|
12610
|
+
if (id)
|
|
12611
|
+
this.files.set(id, file);
|
|
12612
|
+
}
|
|
12613
|
+
}
|
|
12614
|
+
async put(kind, mime, body) {
|
|
12615
|
+
const image = IMAGE_EXT[mime];
|
|
12616
|
+
if (kind === "image" && !image)
|
|
12617
|
+
throw new UserError("not an image format the models accept");
|
|
12618
|
+
const cap = kind === "image" ? IMAGE_MAX_BYTES : UPLOAD_MAX_BYTES;
|
|
12619
|
+
const upload = randomBytes(12).toString("base64url");
|
|
12620
|
+
const file = `${upload}.${kind === "image" ? image : OTHER_EXT}`;
|
|
12621
|
+
const path = join(this.dir, file);
|
|
12622
|
+
const writer = Bun.file(path).writer();
|
|
12623
|
+
let decoder = kind === "file" ? new TextDecoder("utf-8", { fatal: true }) : null;
|
|
12624
|
+
let bytes = 0;
|
|
12625
|
+
try {
|
|
12626
|
+
for await (const chunk of body ?? []) {
|
|
12627
|
+
bytes += chunk.byteLength;
|
|
12628
|
+
if (bytes > cap)
|
|
12629
|
+
throw new UserError(`larger than ${fmtBytes(cap)}`);
|
|
12630
|
+
writer.write(chunk);
|
|
12631
|
+
await writer.flush();
|
|
12632
|
+
if (decoder && (bytes > FILE_MAX_CHARS || !decodes(decoder, chunk)))
|
|
12633
|
+
decoder = null;
|
|
12634
|
+
}
|
|
12635
|
+
if (bytes === 0)
|
|
12636
|
+
throw new UserError("empty file");
|
|
12637
|
+
await writer.end();
|
|
12638
|
+
} catch (e) {
|
|
12639
|
+
await Promise.resolve(writer.end()).catch(() => {});
|
|
12640
|
+
rmSync(path, { force: true });
|
|
12641
|
+
throw e;
|
|
12642
|
+
}
|
|
12643
|
+
this.files.set(upload, file);
|
|
12644
|
+
this.expireUnnamed(upload);
|
|
12645
|
+
return { upload, bytes, text: decoder !== null };
|
|
12646
|
+
}
|
|
12647
|
+
async adopt(from, image) {
|
|
12648
|
+
const upload = randomBytes(12).toString("base64url");
|
|
12649
|
+
const file = `${upload}.${image ? IMAGE_EXT[image] : OTHER_EXT}`;
|
|
12650
|
+
await copyFile(from, join(this.dir, file));
|
|
12651
|
+
this.files.set(upload, file);
|
|
12652
|
+
this.expireUnnamed(upload);
|
|
12653
|
+
return upload;
|
|
12654
|
+
}
|
|
12655
|
+
expireUnnamed(id) {
|
|
12656
|
+
const timer = setTimeout(() => {
|
|
12657
|
+
if (!this.named.has(id) && !this.holds.has(id))
|
|
12658
|
+
this.remove(id);
|
|
12659
|
+
}, this.unnamedMs);
|
|
12660
|
+
timer.unref?.();
|
|
12661
|
+
}
|
|
12662
|
+
path(id) {
|
|
12663
|
+
const file = this.files.get(id);
|
|
12664
|
+
return file ? join(this.dir, file) : null;
|
|
12665
|
+
}
|
|
12666
|
+
imageType(id) {
|
|
12667
|
+
const file = this.files.get(id);
|
|
12668
|
+
return file && MIME_OF_EXT.get(file.slice(file.lastIndexOf(".") + 1)) || null;
|
|
12669
|
+
}
|
|
12670
|
+
has(id) {
|
|
12671
|
+
return this.files.has(id);
|
|
12672
|
+
}
|
|
12673
|
+
hold(ids) {
|
|
12674
|
+
if (ids.some((id) => !this.files.has(id)))
|
|
12675
|
+
throw new UserError(GONE);
|
|
12676
|
+
for (const id of ids)
|
|
12677
|
+
this.holds.set(id, (this.holds.get(id) ?? 0) + 1);
|
|
12678
|
+
}
|
|
12679
|
+
release(ids) {
|
|
12680
|
+
for (const id of ids) {
|
|
12681
|
+
const left = (this.holds.get(id) ?? 0) - 1;
|
|
12682
|
+
if (left > 0) {
|
|
12683
|
+
this.holds.set(id, left);
|
|
12684
|
+
continue;
|
|
12685
|
+
}
|
|
12686
|
+
this.holds.delete(id);
|
|
12687
|
+
if (!this.named.has(id))
|
|
12688
|
+
this.remove(id);
|
|
12689
|
+
}
|
|
12690
|
+
}
|
|
12691
|
+
keep(named) {
|
|
12692
|
+
const before = this.named;
|
|
12693
|
+
this.named = new Set(named);
|
|
12694
|
+
for (const id of before)
|
|
12695
|
+
if (!this.named.has(id) && !this.holds.has(id))
|
|
12696
|
+
this.remove(id);
|
|
12697
|
+
}
|
|
12698
|
+
sweep(named) {
|
|
12699
|
+
this.named = new Set(named);
|
|
12700
|
+
for (const id of [...this.files.keys()])
|
|
12701
|
+
if (!this.named.has(id))
|
|
12702
|
+
this.remove(id);
|
|
12703
|
+
}
|
|
12704
|
+
remove(id) {
|
|
12705
|
+
const file = this.files.get(id);
|
|
12706
|
+
if (!file)
|
|
12707
|
+
return;
|
|
12708
|
+
this.files.delete(id);
|
|
12709
|
+
try {
|
|
12710
|
+
rmSync(join(this.dir, file), { force: true });
|
|
12711
|
+
} catch (e) {
|
|
12712
|
+
log.warn("uploads", `could not delete ${file}`, e);
|
|
12713
|
+
}
|
|
12714
|
+
}
|
|
12715
|
+
}
|
|
12716
|
+
function decodes(decoder, chunk) {
|
|
12717
|
+
try {
|
|
12718
|
+
decoder.decode(chunk, { stream: true });
|
|
12719
|
+
return !chunk.includes(0);
|
|
12720
|
+
} catch {
|
|
12721
|
+
return false;
|
|
12722
|
+
}
|
|
12723
|
+
}
|
|
12724
|
+
|
|
12725
|
+
// ../daemon/src/agent/attachments.ts
|
|
12514
12726
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12515
|
-
var
|
|
12727
|
+
var NAME_MAX = 80;
|
|
12728
|
+
var DRAWN = /^(?:\d{1,6}|[0-9a-f]{16})\.(png|jpg|gif|webp|txt)$/;
|
|
12729
|
+
var NAMED = new RegExp(`^\\d{1,6}-[A-Za-z0-9._-]{1,${NAME_MAX}}$`);
|
|
12730
|
+
var isFile = (file) => DRAWN.test(file) || NAMED.test(file);
|
|
12731
|
+
var MIME = { png: "image/png", jpg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
|
|
12732
|
+
function drawnType(file) {
|
|
12733
|
+
return MIME[DRAWN.exec(file)?.[1] ?? ""] ?? null;
|
|
12734
|
+
}
|
|
12735
|
+
function storedName(name) {
|
|
12736
|
+
const safe = name.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^\.+/, "") || "file";
|
|
12737
|
+
if (safe.length <= NAME_MAX)
|
|
12738
|
+
return safe;
|
|
12739
|
+
const dot = safe.lastIndexOf(".");
|
|
12740
|
+
const ext = dot > 0 && safe.length - dot <= 16 ? safe.slice(dot) : "";
|
|
12741
|
+
return safe.slice(0, NAME_MAX - ext.length) + ext;
|
|
12742
|
+
}
|
|
12516
12743
|
function toolImage(data, mimeType) {
|
|
12517
|
-
const ext =
|
|
12744
|
+
const ext = IMAGE_EXT[mimeType];
|
|
12518
12745
|
if (!ext)
|
|
12519
12746
|
return null;
|
|
12520
12747
|
const bytes = Buffer.from(data, "base64");
|
|
@@ -12523,27 +12750,40 @@ function toolImage(data, mimeType) {
|
|
|
12523
12750
|
const file = `${createHash("sha256").update(bytes).digest("hex").slice(0, 16)}.${ext}`;
|
|
12524
12751
|
return { ref: { file, mimeType, bytes: bytes.length }, bytes };
|
|
12525
12752
|
}
|
|
12526
|
-
var isAttachmentFile = (file) =>
|
|
12753
|
+
var isAttachmentFile = (file) => isFile(file);
|
|
12527
12754
|
function attachmentsDirFor(attachmentsDir, worktreeId) {
|
|
12528
|
-
return
|
|
12755
|
+
return join2(attachmentsDir, worktreeId);
|
|
12756
|
+
}
|
|
12757
|
+
var GONE2 = "the upload is gone; attach it again";
|
|
12758
|
+
async function shortText(path, bytes) {
|
|
12759
|
+
if (bytes > FILE_INLINE_CHARS * 4)
|
|
12760
|
+
return null;
|
|
12761
|
+
try {
|
|
12762
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(await readFile(path));
|
|
12763
|
+
return text.length <= FILE_INLINE_CHARS ? text : null;
|
|
12764
|
+
} catch {
|
|
12765
|
+
return null;
|
|
12766
|
+
}
|
|
12529
12767
|
}
|
|
12530
12768
|
|
|
12531
12769
|
class AttachmentStore {
|
|
12532
12770
|
dir;
|
|
12771
|
+
uploads;
|
|
12533
12772
|
pending = new Map;
|
|
12534
|
-
constructor(dir) {
|
|
12773
|
+
constructor(dir, uploads) {
|
|
12535
12774
|
this.dir = dir;
|
|
12775
|
+
this.uploads = uploads;
|
|
12536
12776
|
}
|
|
12537
12777
|
async putToolImage(worktreeId, file, bytes) {
|
|
12538
|
-
if (!ID.test(worktreeId) || !
|
|
12778
|
+
if (!ID.test(worktreeId) || !DRAWN.test(file))
|
|
12539
12779
|
throw new UserError("bad tool image");
|
|
12540
|
-
const path =
|
|
12780
|
+
const path = join2(attachmentsDirFor(this.dir, worktreeId), file);
|
|
12541
12781
|
if (this.pending.has(path) || await Bun.file(path).exists())
|
|
12542
12782
|
return;
|
|
12543
|
-
await this.write(worktreeId, file, bytes);
|
|
12783
|
+
await this.write(worktreeId, file, (path) => writeFile(path, bytes));
|
|
12544
12784
|
}
|
|
12545
12785
|
whenWritten(worktreeId, file) {
|
|
12546
|
-
return this.pending.get(
|
|
12786
|
+
return this.pending.get(join2(attachmentsDirFor(this.dir, worktreeId), file)) ?? Promise.resolve();
|
|
12547
12787
|
}
|
|
12548
12788
|
async put(worktreeId, n, input) {
|
|
12549
12789
|
if (!ID.test(worktreeId))
|
|
@@ -12553,19 +12793,23 @@ class AttachmentStore {
|
|
|
12553
12793
|
return this.putImage(worktreeId, n, input);
|
|
12554
12794
|
case "paste":
|
|
12555
12795
|
return this.putText(worktreeId, n, input);
|
|
12796
|
+
case "file":
|
|
12797
|
+
return this.putFile(worktreeId, n, input);
|
|
12556
12798
|
case "pick":
|
|
12557
12799
|
return { kind: "pick", ref: { ...input, n } };
|
|
12558
12800
|
}
|
|
12559
12801
|
}
|
|
12560
12802
|
async putImage(worktreeId, n, img) {
|
|
12561
|
-
const
|
|
12562
|
-
if (
|
|
12563
|
-
throw new UserError(`${img.name}:
|
|
12564
|
-
const
|
|
12565
|
-
|
|
12803
|
+
const from = this.uploads.path(img.upload);
|
|
12804
|
+
if (!from || this.uploads.imageType(img.upload) !== img.mimeType)
|
|
12805
|
+
throw new UserError(`${img.name}: ${GONE2}`);
|
|
12806
|
+
const bytes = await readFile(from);
|
|
12807
|
+
const file = `${n}.${IMAGE_EXT[img.mimeType]}`;
|
|
12808
|
+
const path = await this.write(worktreeId, file, (to) => writeFile(to, bytes));
|
|
12566
12809
|
return {
|
|
12567
12810
|
kind: "image",
|
|
12568
12811
|
bytes,
|
|
12812
|
+
path,
|
|
12569
12813
|
ref: {
|
|
12570
12814
|
kind: "image",
|
|
12571
12815
|
n,
|
|
@@ -12578,28 +12822,78 @@ class AttachmentStore {
|
|
|
12578
12822
|
}
|
|
12579
12823
|
};
|
|
12580
12824
|
}
|
|
12825
|
+
async putFile(worktreeId, n, input) {
|
|
12826
|
+
const from = this.uploads.path(input.upload);
|
|
12827
|
+
if (!from)
|
|
12828
|
+
throw new UserError(`${input.name}: ${GONE2}`);
|
|
12829
|
+
const file = `${n}-${storedName(input.name)}`;
|
|
12830
|
+
const path = await this.write(worktreeId, file, (to) => copyFile2(from, to));
|
|
12831
|
+
const bytes = (await stat(path)).size;
|
|
12832
|
+
const text = input.text ? await shortText(path, bytes) : null;
|
|
12833
|
+
return {
|
|
12834
|
+
kind: "file",
|
|
12835
|
+
path,
|
|
12836
|
+
...text === null ? {} : { text },
|
|
12837
|
+
ref: { kind: "file", n, name: input.name, bytes, text: input.text, file }
|
|
12838
|
+
};
|
|
12839
|
+
}
|
|
12581
12840
|
async putText(worktreeId, n, { text, name, source }) {
|
|
12582
12841
|
if (text.length === 0)
|
|
12583
12842
|
throw new UserError("empty paste");
|
|
12584
12843
|
const file = `${n}.txt`;
|
|
12585
|
-
await this.write(worktreeId, file, text);
|
|
12844
|
+
await this.write(worktreeId, file, (to) => writeFile(to, text));
|
|
12586
12845
|
return {
|
|
12587
12846
|
kind: "paste",
|
|
12588
12847
|
text,
|
|
12589
12848
|
ref: { kind: "paste", n, ...name ? { name } : {}, ...source ? { source } : {}, ...pasteSummary(text), file }
|
|
12590
12849
|
};
|
|
12591
12850
|
}
|
|
12592
|
-
|
|
12851
|
+
async reattach(worktreeId, ref, archived) {
|
|
12852
|
+
const again = await this.inputOf(worktreeId, ref, archived);
|
|
12853
|
+
const parsed = attachmentInputSchema.safeParse(again);
|
|
12854
|
+
return parsed.success ? parsed.data : null;
|
|
12855
|
+
}
|
|
12856
|
+
async inputOf(worktreeId, ref, archived) {
|
|
12857
|
+
if (ref.kind === "pick") {
|
|
12858
|
+
const { n: _n, ...pick } = ref;
|
|
12859
|
+
return pick;
|
|
12860
|
+
}
|
|
12861
|
+
let path = null;
|
|
12862
|
+
for (const at of [this.fileFor(worktreeId, ref.file), archived?.(ref.file) ?? null]) {
|
|
12863
|
+
if (at && await Bun.file(at).exists()) {
|
|
12864
|
+
path = at;
|
|
12865
|
+
break;
|
|
12866
|
+
}
|
|
12867
|
+
}
|
|
12868
|
+
if (!path)
|
|
12869
|
+
return null;
|
|
12870
|
+
if (ref.kind === "paste") {
|
|
12871
|
+
const { name, source } = ref;
|
|
12872
|
+
return { kind: "paste", text: await readFile(path, "utf8"), name, source };
|
|
12873
|
+
}
|
|
12874
|
+
if (ref.kind === "file") {
|
|
12875
|
+
const upload = await this.uploads.adopt(path, null);
|
|
12876
|
+
return { kind: "file", upload, name: ref.name, bytes: ref.bytes, text: ref.text };
|
|
12877
|
+
}
|
|
12878
|
+
const mimeType = IMAGE_MIME_TYPES.find((m) => m === ref.mimeType);
|
|
12879
|
+
if (!mimeType)
|
|
12880
|
+
return null;
|
|
12881
|
+
const upload = await this.uploads.adopt(path, mimeType);
|
|
12882
|
+
const { name, bytes, width, height } = ref;
|
|
12883
|
+
return { kind: "image", upload, name, mimeType, bytes, width, height };
|
|
12884
|
+
}
|
|
12885
|
+
async write(worktreeId, file, fill) {
|
|
12593
12886
|
const wtDir = attachmentsDirFor(this.dir, worktreeId);
|
|
12594
|
-
const path =
|
|
12595
|
-
const done = mkdir(wtDir, { recursive: true }).then(() =>
|
|
12887
|
+
const path = join2(wtDir, file);
|
|
12888
|
+
const done = mkdir(wtDir, { recursive: true }).then(() => fill(path)).finally(() => this.pending.delete(path));
|
|
12596
12889
|
this.pending.set(path, done);
|
|
12597
|
-
|
|
12890
|
+
await done;
|
|
12891
|
+
return path;
|
|
12598
12892
|
}
|
|
12599
12893
|
fileFor(worktreeId, file) {
|
|
12600
|
-
if (!ID.test(worktreeId) || !
|
|
12894
|
+
if (!ID.test(worktreeId) || !isFile(file))
|
|
12601
12895
|
return null;
|
|
12602
|
-
return
|
|
12896
|
+
return join2(attachmentsDirFor(this.dir, worktreeId), file);
|
|
12603
12897
|
}
|
|
12604
12898
|
}
|
|
12605
12899
|
|
|
@@ -12756,9 +13050,9 @@ class OptionProbe {
|
|
|
12756
13050
|
}
|
|
12757
13051
|
|
|
12758
13052
|
// ../daemon/src/agent/registry.ts
|
|
12759
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
13053
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
12760
13054
|
import { homedir as homedir3 } from "os";
|
|
12761
|
-
import { dirname as dirname3, isAbsolute, join as
|
|
13055
|
+
import { dirname as dirname3, isAbsolute, join as join6, resolve as resolve3 } from "path";
|
|
12762
13056
|
|
|
12763
13057
|
// ../daemon/src/core/cloud.ts
|
|
12764
13058
|
var enabled = process.env.TOYON_CLOUD === "1";
|
|
@@ -12785,7 +13079,7 @@ var cloud = {
|
|
|
12785
13079
|
import { spawn as spawn2 } from "child_process";
|
|
12786
13080
|
import { createHash as createHash2 } from "crypto";
|
|
12787
13081
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
12788
|
-
import { join as
|
|
13082
|
+
import { join as join3 } from "path";
|
|
12789
13083
|
|
|
12790
13084
|
// ../daemon/src/core/metrics.ts
|
|
12791
13085
|
var WARN_MS = 50;
|
|
@@ -12827,7 +13121,7 @@ function lockfileHash(dir) {
|
|
|
12827
13121
|
const h = createHash2("sha1");
|
|
12828
13122
|
for (const f of LOCKFILES) {
|
|
12829
13123
|
try {
|
|
12830
|
-
h.update(readFileSync2(
|
|
13124
|
+
h.update(readFileSync2(join3(dir, f)));
|
|
12831
13125
|
} catch {}
|
|
12832
13126
|
}
|
|
12833
13127
|
return h.digest("hex");
|
|
@@ -12939,8 +13233,9 @@ async function runLive(cmd, args, cwd, opts = {}) {
|
|
|
12939
13233
|
}
|
|
12940
13234
|
}
|
|
12941
13235
|
function watchHooks(stream, onHook) {
|
|
13236
|
+
let failed;
|
|
12942
13237
|
if (!stream)
|
|
12943
|
-
return;
|
|
13238
|
+
return () => failed;
|
|
12944
13239
|
const dec = new TextDecoder;
|
|
12945
13240
|
let buf = "";
|
|
12946
13241
|
const open = new Map;
|
|
@@ -12961,11 +13256,15 @@ function watchHooks(stream, onHook) {
|
|
|
12961
13256
|
if (ev.event === "child_start" && ev.child_class === "hook" && ev.hook_name) {
|
|
12962
13257
|
open.set(ev.child_id, ev.hook_name);
|
|
12963
13258
|
onHook(ev.hook_name);
|
|
12964
|
-
} else if (ev.event === "child_exit" && open.
|
|
13259
|
+
} else if (ev.event === "child_exit" && open.has(ev.child_id)) {
|
|
13260
|
+
if (ev.code)
|
|
13261
|
+
failed = open.get(ev.child_id);
|
|
13262
|
+
open.delete(ev.child_id);
|
|
12965
13263
|
onHook(open.size > 0 ? [...open.values()].pop() : undefined);
|
|
12966
13264
|
}
|
|
12967
13265
|
}
|
|
12968
13266
|
});
|
|
13267
|
+
return () => failed;
|
|
12969
13268
|
}
|
|
12970
13269
|
function runWatched(cmd, args, cwd, opts = {}) {
|
|
12971
13270
|
const started = Date.now();
|
|
@@ -13010,8 +13309,7 @@ function runWatched(cmd, args, cwd, opts = {}) {
|
|
|
13010
13309
|
};
|
|
13011
13310
|
if (child.pid)
|
|
13012
13311
|
opts.onSpawn?.(child.pid, kill);
|
|
13013
|
-
|
|
13014
|
-
watchHooks(child.stdio[3], opts.onHook);
|
|
13312
|
+
const failedHook = opts.onHook ? watchHooks(child.stdio[3], opts.onHook) : undefined;
|
|
13015
13313
|
if (opts.timeoutMs) {
|
|
13016
13314
|
timer = setTimeout(() => {
|
|
13017
13315
|
timedOut = true;
|
|
@@ -13047,7 +13345,8 @@ function runWatched(cmd, args, cwd, opts = {}) {
|
|
|
13047
13345
|
err: err.join("").trim(),
|
|
13048
13346
|
exit,
|
|
13049
13347
|
text,
|
|
13050
|
-
...timedOut && opts.timeoutMs ? { ceilingMs: opts.timeoutMs } : {}
|
|
13348
|
+
...timedOut && opts.timeoutMs ? { ceilingMs: opts.timeoutMs } : {},
|
|
13349
|
+
...failedHook?.() ? { hook: failedHook() } : {}
|
|
13051
13350
|
});
|
|
13052
13351
|
});
|
|
13053
13352
|
});
|
|
@@ -13142,7 +13441,7 @@ function confine(launch, b, extraWrite = [], platform = process.platform, which
|
|
|
13142
13441
|
}
|
|
13143
13442
|
|
|
13144
13443
|
// ../daemon/src/agent/native.ts
|
|
13145
|
-
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
13444
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
13146
13445
|
var OS = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
13147
13446
|
function nativePackage(pkg, host) {
|
|
13148
13447
|
const os = OS[host.platform];
|
|
@@ -13165,7 +13464,7 @@ function hostTraits() {
|
|
|
13165
13464
|
}
|
|
13166
13465
|
function muslLoader() {
|
|
13167
13466
|
try {
|
|
13168
|
-
return existsSync2("/lib") &&
|
|
13467
|
+
return existsSync2("/lib") && readdirSync2("/lib").some((f) => f.startsWith("ld-musl-"));
|
|
13169
13468
|
} catch {
|
|
13170
13469
|
return false;
|
|
13171
13470
|
}
|
|
@@ -13235,49 +13534,52 @@ function opencodeEnv() {
|
|
|
13235
13534
|
}
|
|
13236
13535
|
|
|
13237
13536
|
// ../daemon/src/agent/sandbox.ts
|
|
13238
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
13537
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
13239
13538
|
import { homedir as homedir2, tmpdir } from "os";
|
|
13240
|
-
import { join as
|
|
13539
|
+
import { join as join5, resolve as resolve2 } from "path";
|
|
13241
13540
|
|
|
13242
13541
|
// ../daemon/src/core/paths.ts
|
|
13243
|
-
import { mkdirSync } from "fs";
|
|
13542
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
13244
13543
|
import { homedir } from "os";
|
|
13245
|
-
import { join as
|
|
13246
|
-
function makePaths(home = process.env.TOYON_HOME ??
|
|
13544
|
+
import { join as join4 } from "path";
|
|
13545
|
+
function makePaths(home = process.env.TOYON_HOME ?? join4(homedir(), ".toyon")) {
|
|
13247
13546
|
return {
|
|
13248
13547
|
home,
|
|
13249
|
-
stateFile:
|
|
13250
|
-
draftsFile:
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13548
|
+
stateFile: join4(home, DAEMON_FILES.state),
|
|
13549
|
+
draftsFile: join4(home, DAEMON_FILES.drafts),
|
|
13550
|
+
draftAttachmentsFile: join4(home, "draft-attachments.json"),
|
|
13551
|
+
tokenFile: join4(home, DAEMON_FILES.token),
|
|
13552
|
+
pidFile: join4(home, DAEMON_FILES.pid),
|
|
13553
|
+
logFile: join4(home, DAEMON_FILES.log),
|
|
13554
|
+
remoteFile: join4(home, DAEMON_FILES.remote),
|
|
13555
|
+
transcriptsDir: join4(home, "transcripts"),
|
|
13556
|
+
attachmentsDir: join4(home, "attachments"),
|
|
13557
|
+
uploadsDir: join4(home, "uploads"),
|
|
13558
|
+
archiveDir: join4(home, "archive"),
|
|
13559
|
+
worktreesDir: join4(home, "worktrees.noindex"),
|
|
13560
|
+
cacheDir: join4(home, "cache.noindex"),
|
|
13561
|
+
themesDir: join4(home, "themes"),
|
|
13562
|
+
agentsDir: process.env.TOYON_AGENTS_DIR ?? join4(home, "agents"),
|
|
13563
|
+
agentsFile: join4(home, "agents.json"),
|
|
13564
|
+
scratchDir: join4(home, "scratch"),
|
|
13565
|
+
gitCredentialsFile: join4(home, "git-credentials")
|
|
13265
13566
|
};
|
|
13266
13567
|
}
|
|
13267
13568
|
function ensureDirs(p) {
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13569
|
+
mkdirSync2(p.home, { recursive: true, mode: 448 });
|
|
13570
|
+
mkdirSync2(p.transcriptsDir, { recursive: true });
|
|
13571
|
+
mkdirSync2(p.attachmentsDir, { recursive: true });
|
|
13572
|
+
mkdirSync2(p.uploadsDir, { recursive: true });
|
|
13573
|
+
mkdirSync2(p.archiveDir, { recursive: true });
|
|
13574
|
+
mkdirSync2(p.worktreesDir, { recursive: true });
|
|
13575
|
+
mkdirSync2(p.cacheDir, { recursive: true });
|
|
13576
|
+
mkdirSync2(p.themesDir, { recursive: true });
|
|
13577
|
+
mkdirSync2(p.agentsDir, { recursive: true });
|
|
13578
|
+
mkdirSync2(p.scratchDir, { recursive: true });
|
|
13277
13579
|
}
|
|
13278
13580
|
|
|
13279
13581
|
// ../daemon/src/git/exclude.ts
|
|
13280
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
13582
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
13281
13583
|
import { dirname } from "path";
|
|
13282
13584
|
async function excludeFile(cwd) {
|
|
13283
13585
|
const r = await git(cwd, "rev-parse", "--path-format=absolute", "--git-path", "info/exclude");
|
|
@@ -13294,7 +13596,7 @@ async function excludeFromGit(cwd, pattern) {
|
|
|
13294
13596
|
if (current.split(`
|
|
13295
13597
|
`).some((l) => l.trim() === pattern))
|
|
13296
13598
|
return;
|
|
13297
|
-
|
|
13599
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
13298
13600
|
writeFileSync(file, `${current}${current && !current.endsWith(`
|
|
13299
13601
|
`) ? `
|
|
13300
13602
|
` : ""}${pattern}
|
|
@@ -13320,7 +13622,7 @@ async function excludeBlock(cwd, name, patterns) {
|
|
|
13320
13622
|
` : "";
|
|
13321
13623
|
if (text === current)
|
|
13322
13624
|
return;
|
|
13323
|
-
|
|
13625
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
13324
13626
|
writeFileSync(file, text);
|
|
13325
13627
|
}
|
|
13326
13628
|
async function unexcludeFromGit(cwd, pattern) {
|
|
@@ -13387,7 +13689,7 @@ async function prepareLaunch(cwd, spec) {
|
|
|
13387
13689
|
log.warn(cwd, `agent ${spec.id} runs without an OS sandbox`);
|
|
13388
13690
|
return { bounds, env: setup?.env ?? {} };
|
|
13389
13691
|
}
|
|
13390
|
-
var SETTINGS_REL =
|
|
13692
|
+
var SETTINGS_REL = join5(".claude", "settings.local.json");
|
|
13391
13693
|
function claudeLocalSettings(b) {
|
|
13392
13694
|
return {
|
|
13393
13695
|
sandbox: {
|
|
@@ -13401,8 +13703,8 @@ function claudeLocalSettings(b) {
|
|
|
13401
13703
|
};
|
|
13402
13704
|
}
|
|
13403
13705
|
async function writeClaudeLocalSettings(cwd, b) {
|
|
13404
|
-
const file =
|
|
13405
|
-
|
|
13706
|
+
const file = join5(cwd, SETTINGS_REL);
|
|
13707
|
+
mkdirSync4(join5(cwd, ".claude"), { recursive: true });
|
|
13406
13708
|
let existing = {};
|
|
13407
13709
|
if (existsSync5(file)) {
|
|
13408
13710
|
try {
|
|
@@ -13424,7 +13726,7 @@ async function writeClaudeLocalSettings(cwd, b) {
|
|
|
13424
13726
|
writeFileSync2(tmp, `${JSON.stringify(next, null, 2)}
|
|
13425
13727
|
`);
|
|
13426
13728
|
renameSync(tmp, file);
|
|
13427
|
-
if (existsSync5(
|
|
13729
|
+
if (existsSync5(join5(cwd, ".git")))
|
|
13428
13730
|
await excludeFromGit(cwd, SETTINGS_REL);
|
|
13429
13731
|
}
|
|
13430
13732
|
|
|
@@ -13549,12 +13851,12 @@ class AgentRegistry {
|
|
|
13549
13851
|
const name = this.packageName(spec);
|
|
13550
13852
|
if (!name || spec.run.kind === "command")
|
|
13551
13853
|
return null;
|
|
13552
|
-
const pkgDir =
|
|
13854
|
+
const pkgDir = join6(this.dir, spec.id, "node_modules", name);
|
|
13553
13855
|
if (spec.run.kind === "npm-native") {
|
|
13554
|
-
const bin =
|
|
13856
|
+
const bin = join6(pkgDir, spec.run.bin);
|
|
13555
13857
|
return existsSync6(bin) ? bin : null;
|
|
13556
13858
|
}
|
|
13557
|
-
const pkgJson =
|
|
13859
|
+
const pkgJson = join6(pkgDir, "package.json");
|
|
13558
13860
|
if (!existsSync6(pkgJson))
|
|
13559
13861
|
return null;
|
|
13560
13862
|
try {
|
|
@@ -13562,7 +13864,7 @@ class AgentRegistry {
|
|
|
13562
13864
|
const rel = typeof meta.bin === "string" ? meta.bin : meta.bin?.[spec.run.bin];
|
|
13563
13865
|
if (!rel)
|
|
13564
13866
|
return null;
|
|
13565
|
-
const script =
|
|
13867
|
+
const script = join6(dirname3(pkgJson), rel);
|
|
13566
13868
|
return existsSync6(script) ? script : null;
|
|
13567
13869
|
} catch (e) {
|
|
13568
13870
|
log.warn("agents", `cannot read ${pkgJson}`, e);
|
|
@@ -13616,10 +13918,10 @@ class AgentRegistry {
|
|
|
13616
13918
|
if (this.installed(spec) && this.installedVersion(spec) === spec.run.version)
|
|
13617
13919
|
return Promise.resolve();
|
|
13618
13920
|
const { version } = spec.run;
|
|
13619
|
-
const dir =
|
|
13921
|
+
const dir = join6(this.dir, id);
|
|
13620
13922
|
const p = (async () => {
|
|
13621
|
-
|
|
13622
|
-
writeFileSync3(
|
|
13923
|
+
mkdirSync5(dir, { recursive: true });
|
|
13924
|
+
writeFileSync3(join6(dir, "package.json"), `${JSON.stringify({ name: `toyon-agent-${id}`, private: true, dependencies: { [pkg]: version } }, null, 2)}
|
|
13623
13925
|
`);
|
|
13624
13926
|
log.info("agents", `installing ${pkg}@${version} for ${id}`);
|
|
13625
13927
|
const r = await this.installer(dir, pkg, version);
|
|
@@ -13643,7 +13945,7 @@ class AgentRegistry {
|
|
|
13643
13945
|
const name = this.packageName(spec);
|
|
13644
13946
|
if (!name)
|
|
13645
13947
|
return null;
|
|
13646
|
-
const pkgJson =
|
|
13948
|
+
const pkgJson = join6(this.dir, spec.id, "node_modules", name, "package.json");
|
|
13647
13949
|
try {
|
|
13648
13950
|
return JSON.parse(readFileSync6(pkgJson, "utf8")).version ?? null;
|
|
13649
13951
|
} catch {
|
|
@@ -13773,6 +14075,12 @@ ${body}
|
|
|
13773
14075
|
return parts.join(`
|
|
13774
14076
|
`);
|
|
13775
14077
|
}
|
|
14078
|
+
function unformatOutput(output) {
|
|
14079
|
+
if (!output.startsWith("```\n"))
|
|
14080
|
+
return "";
|
|
14081
|
+
const close = output.lastIndexOf("\n```");
|
|
14082
|
+
return close < 4 ? "" : output.slice(4, close);
|
|
14083
|
+
}
|
|
13776
14084
|
|
|
13777
14085
|
// ../daemon/src/agent/policy.ts
|
|
13778
14086
|
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
@@ -13881,6 +14189,7 @@ var SYSTEM_APPEND = [
|
|
|
13881
14189
|
"Never run `git commit` unless the user explicitly asks you to, and never offer to, through the question tool or otherwise; leave changes uncommitted for the user to review and commit themselves.",
|
|
13882
14190
|
"Keep the scope tight: do the asked task well, then stop. A follow-up the user might want later is a sentence in your reply, not a question.",
|
|
13883
14191
|
"When the work is done and the only thing left is whether to do something you recommend, do not end your turn on a prose offer waiting for a yes. Ask it with your question tool when you have one: one question, two options with the recommended one first, each saying what it changes, and typed text allowed so the answer can carry instructions. The card is the first thing the user reads, before any prose of yours, so the question itself states what you propose and why in a sentence or two; a question that is only the yes-or-no leaves them guessing what it is. Ask this way only where you would otherwise have stopped and waited; never to confirm work already asked for.",
|
|
14192
|
+
"An answer to a question of yours can carry typed text beside the option picked, and the text outranks the pick. Text that doubts the pick, asks you something or says the user cannot decide is not a yes: answer it in prose and change nothing until they have decided. Text that only adds detail to the pick is an instruction for carrying it out.",
|
|
13884
14193
|
"When referencing a file in chat, use a Markdown link whose target is its absolute path, with an optional `:line`; Toyon opens it in the built-in editor, whether it is in this worktree or anywhere else on this machine. A folder, a URL route or a command is not a file: name it in backticks, never as a link.",
|
|
13885
14194
|
"`@some/path` in a message means read that file or directory first; `@changes` means this worktree's uncommitted files, which `git status` lists.",
|
|
13886
14195
|
'Toyon previews the project in a browser panel by running the commands in its settings file, .toyon/settings.json (or toyon.json at the repo root, in a repo that keeps it there), shaped like {"setup": ["bun install"], "run": {"web": "bun run dev --port $PORT"}, "check": "bun run check", "land": {"route": "merge"}}: setup runs once in every new worktree, teardown (optional) runs once when a worktree is removed, every command in run keeps running and must listen on $PORT, which toyon sets differently for each worktree, check (optional) runs after each of your turns and must pass before the work can land, and land (optional) says how the user lands work on main: route "merge" (onto main here), "push" (onto main here, then pushed) or "pr" (a pull request); method "merge", "squash" or "rebase" for how the commits arrive on main; automerge true when GitHub should merge the pull request itself; timeouts (optional) gives setup, check and commit each a ceiling as a duration like "30m" (ten minutes each when unset), for a suite that runs longer. A settings.local.json beside it (toyon.local.json at the root) holds one person\'s overrides and is never committed. Both files may carry comments and trailing commas; leave any you find.',
|
|
@@ -13888,6 +14197,113 @@ var SYSTEM_APPEND = [
|
|
|
13888
14197
|
"When you scaffold a project, write its .gitignore (dependencies, build output, local env files) before installing anything, so an install never leaves thousands of untracked files for the user to wade through or commit.",
|
|
13889
14198
|
"When you scaffold a project or change how it installs or starts, finish by updating the settings file Toyon already reads, or writing .toyon/settings.json when there is none, so the preview can run it."
|
|
13890
14199
|
].join(" ");
|
|
14200
|
+
function failedRun(f) {
|
|
14201
|
+
switch (f.kind) {
|
|
14202
|
+
case "hook":
|
|
14203
|
+
case "check":
|
|
14204
|
+
case "command":
|
|
14205
|
+
return f;
|
|
14206
|
+
case "conflict":
|
|
14207
|
+
return f.step;
|
|
14208
|
+
case "preview":
|
|
14209
|
+
return;
|
|
14210
|
+
}
|
|
14211
|
+
}
|
|
14212
|
+
function fixPrompt(f) {
|
|
14213
|
+
switch (f.kind) {
|
|
14214
|
+
case "hook":
|
|
14215
|
+
return hookFixPrompt(f.hook);
|
|
14216
|
+
case "conflict":
|
|
14217
|
+
return conflictFixPrompt(f.base, f.how, !!f.step);
|
|
14218
|
+
case "check":
|
|
14219
|
+
return checkFixPrompt(f.command);
|
|
14220
|
+
case "command":
|
|
14221
|
+
return commandFixPrompt(f.command);
|
|
14222
|
+
case "preview":
|
|
14223
|
+
return procFixPrompt(f.procs, f.log);
|
|
14224
|
+
}
|
|
14225
|
+
}
|
|
14226
|
+
var WHY_COMMAND_CHARS = 60;
|
|
14227
|
+
function fixWhy(f) {
|
|
14228
|
+
switch (f.kind) {
|
|
14229
|
+
case "hook":
|
|
14230
|
+
return `the ${f.hook} hook refused the ${f.hook.includes("push") ? "push" : "commit"}`;
|
|
14231
|
+
case "conflict":
|
|
14232
|
+
return f.how === "rebase" ? `the branch needs a rebase onto ${f.base}` : `the branch needs ${f.base} merged in`;
|
|
14233
|
+
case "check":
|
|
14234
|
+
return "the check failed";
|
|
14235
|
+
case "command": {
|
|
14236
|
+
const line = f.command.split(`
|
|
14237
|
+
`)[0] ?? "";
|
|
14238
|
+
return `\`${line.length > WHY_COMMAND_CHARS ? `${line.slice(0, WHY_COMMAND_CHARS)}...` : line}\` failed`;
|
|
14239
|
+
}
|
|
14240
|
+
case "preview":
|
|
14241
|
+
return "the dev server is not reachable";
|
|
14242
|
+
}
|
|
14243
|
+
}
|
|
14244
|
+
function failureContext(f) {
|
|
14245
|
+
const run = failedRun(f);
|
|
14246
|
+
if (!run)
|
|
14247
|
+
return;
|
|
14248
|
+
const shown = clipOutput(run.text.replace(/\n+$/, ""));
|
|
14249
|
+
const what = f.kind === "command" ? "The command that failed" : "What Toyon ran";
|
|
14250
|
+
return `${what}, and what it printed:
|
|
14251
|
+
$ ${run.command}${shown ? `
|
|
14252
|
+
${shown}` : `
|
|
14253
|
+
(it printed nothing)`}`;
|
|
14254
|
+
}
|
|
14255
|
+
function hookFixPrompt(hook) {
|
|
14256
|
+
return [
|
|
14257
|
+
`The ${hook} hook refused that, and what it printed is attached below.`,
|
|
14258
|
+
"Fix what it complains about, inside this worktree.",
|
|
14259
|
+
"Do not skip the hook, do not edit it or its configuration to get past it, and do not commit: Toyon runs the check when your turn ends, and the user lands from there."
|
|
14260
|
+
].join(" ");
|
|
14261
|
+
}
|
|
14262
|
+
function conflictFixPrompt(base, how, shown) {
|
|
14263
|
+
const ask = how === "rebase" ? `Rebase this branch onto ${base} and resolve the conflicts, keeping any uncommitted changes, then verify the app still works.` : `Merge ${base} into this branch, resolve the conflicts and commit the merge, then verify the app still works.`;
|
|
14264
|
+
return shown ? `${ask} What git printed when Toyon tried is attached below.` : ask;
|
|
14265
|
+
}
|
|
14266
|
+
function checkFixPrompt(command) {
|
|
14267
|
+
return [
|
|
14268
|
+
`The repo's check, \`${command}\`, failed, and what it printed is attached below.`,
|
|
14269
|
+
"Fix what it reports, inside this worktree.",
|
|
14270
|
+
"Do not weaken the check or its configuration to get past it. Toyon runs it again when your turn ends."
|
|
14271
|
+
].join(" ");
|
|
14272
|
+
}
|
|
14273
|
+
function commandFixPrompt(command) {
|
|
14274
|
+
return [
|
|
14275
|
+
`The user ran \`${command}\` and it failed; what it printed is attached below.`,
|
|
14276
|
+
"Find the cause and fix it, inside this worktree.",
|
|
14277
|
+
"If the cause is not in this worktree, say what it is instead of changing anything."
|
|
14278
|
+
].join(" ");
|
|
14279
|
+
}
|
|
14280
|
+
var PROC_TAIL = 40;
|
|
14281
|
+
function procFixPrompt(procs, log) {
|
|
14282
|
+
const bad = procs.filter((p) => p.status === "crashed" || p.status === "unreachable");
|
|
14283
|
+
const lines = bad.map((p) => `- \`${p.name}\`: \`${p.command}\`, started with PORT=${p.port}. ${procDiagnosis(p)}`);
|
|
14284
|
+
const tail = log.slice(-PROC_TAIL).map((l) => `[${l.proc}] ${l.line}`).join(`
|
|
14285
|
+
`);
|
|
14286
|
+
return [
|
|
14287
|
+
"The dev server in this worktree is not reachable, so the preview is empty.",
|
|
14288
|
+
"",
|
|
14289
|
+
...lines,
|
|
14290
|
+
"",
|
|
14291
|
+
tail ? `Last output:
|
|
14292
|
+
\`\`\`
|
|
14293
|
+
${tail}
|
|
14294
|
+
\`\`\`` : "It produced no output.",
|
|
14295
|
+
"",
|
|
14296
|
+
"Find the cause and fix it. The command must run in the foreground and listen on the port in the PORT environment variable, which Toyon sets differently for each worktree. If the tool takes its port from a flag instead (Vite does), make the app read PORT, for Vite `server.port: Number(process.env.PORT)` with `strictPort: true`, or add the flag to the start command in toyon's settings file. When your turn ends toyon restarts the process and checks again."
|
|
14297
|
+
].join(`
|
|
14298
|
+
`);
|
|
14299
|
+
}
|
|
14300
|
+
function procDiagnosis(p) {
|
|
14301
|
+
if (p.detail)
|
|
14302
|
+
return p.detail;
|
|
14303
|
+
if (p.status === "crashed")
|
|
14304
|
+
return p.exitCode != null ? `It exited with code ${p.exitCode}.` : "It crashed.";
|
|
14305
|
+
return "It never answered on that port.";
|
|
14306
|
+
}
|
|
13891
14307
|
function ambientBlock(paragraphs) {
|
|
13892
14308
|
const said = paragraphs.filter((p) => !!p);
|
|
13893
14309
|
if (said.length === 0)
|
|
@@ -13906,6 +14322,9 @@ function previewContext(p) {
|
|
|
13906
14322
|
case "setup":
|
|
13907
14323
|
line = "Toyon is setting this worktree up and starts the preview when that is done; the next message will say where it answers.";
|
|
13908
14324
|
break;
|
|
14325
|
+
case "parked":
|
|
14326
|
+
line = "The preview is off: Toyon has not started this worktree's dev servers, and starts them when you first write a file or when the user asks for them. Nothing answers until then.";
|
|
14327
|
+
break;
|
|
13909
14328
|
case "starting":
|
|
13910
14329
|
line = `Toyon is starting the preview; it answers${at} once it is up.`;
|
|
13911
14330
|
break;
|
|
@@ -13920,8 +14339,12 @@ function previewContext(p) {
|
|
|
13920
14339
|
}
|
|
13921
14340
|
return line;
|
|
13922
14341
|
}
|
|
13923
|
-
function imageCaption(ref) {
|
|
13924
|
-
return `${attachmentLabel("image", ref.n)}: ${ref.name} (${ref.width}\xD7${ref.height})`;
|
|
14342
|
+
function imageCaption(ref, path) {
|
|
14343
|
+
return `${attachmentLabel("image", ref.n)}: ${ref.name} (${ref.width}\xD7${ref.height}), saved at ${path}`;
|
|
14344
|
+
}
|
|
14345
|
+
function fileCaption(ref, path, inline = false) {
|
|
14346
|
+
const at = `A file the user attached: ${ref.name} (${fmtBytes(ref.bytes)}), saved at ${path}.`;
|
|
14347
|
+
return inline ? at : `${at} Read or search it there.`;
|
|
13925
14348
|
}
|
|
13926
14349
|
function pasteCaption(ref) {
|
|
13927
14350
|
const label = attachmentLabel("paste", ref.n);
|
|
@@ -13967,7 +14390,7 @@ function attachmentBlocks(a) {
|
|
|
13967
14390
|
switch (a.kind) {
|
|
13968
14391
|
case "image":
|
|
13969
14392
|
return [
|
|
13970
|
-
textBlock(imageCaption(a.ref)),
|
|
14393
|
+
textBlock(imageCaption(a.ref, a.path)),
|
|
13971
14394
|
{ type: "image", mimeType: a.ref.mimeType, data: a.bytes.toString("base64") }
|
|
13972
14395
|
];
|
|
13973
14396
|
case "paste":
|
|
@@ -13975,6 +14398,13 @@ function attachmentBlocks(a) {
|
|
|
13975
14398
|
<pasted-text ${a.ref.n}>
|
|
13976
14399
|
${a.text}
|
|
13977
14400
|
</pasted-text>`)];
|
|
14401
|
+
case "file":
|
|
14402
|
+
return [
|
|
14403
|
+
textBlock(a.text === undefined ? fileCaption(a.ref, a.path) : `${fileCaption(a.ref, a.path, true)}
|
|
14404
|
+
<attached-file ${a.ref.n}>
|
|
14405
|
+
${a.text}
|
|
14406
|
+
</attached-file>`)
|
|
14407
|
+
];
|
|
13978
14408
|
case "pick":
|
|
13979
14409
|
return [textBlock(pickCaption(a.ref))];
|
|
13980
14410
|
}
|
|
@@ -13983,9 +14413,9 @@ ${a.text}
|
|
|
13983
14413
|
// ../daemon/src/agent/transcript.ts
|
|
13984
14414
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
13985
14415
|
import { appendFile } from "fs/promises";
|
|
13986
|
-
import { join as
|
|
14416
|
+
import { join as join7 } from "path";
|
|
13987
14417
|
function transcriptPathFor(transcriptsDir, worktreeId) {
|
|
13988
|
-
return
|
|
14418
|
+
return join7(transcriptsDir, `${worktreeId}.jsonl`);
|
|
13989
14419
|
}
|
|
13990
14420
|
function coalesce(entries) {
|
|
13991
14421
|
const out = [];
|
|
@@ -14140,7 +14570,7 @@ async function cleanUp(ctx, a, sessionId) {
|
|
|
14140
14570
|
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
14141
14571
|
import { open as open2 } from "fs/promises";
|
|
14142
14572
|
import { homedir as homedir4 } from "os";
|
|
14143
|
-
import { basename as basename2, dirname as dirname4, join as
|
|
14573
|
+
import { basename as basename2, dirname as dirname4, join as join8 } from "path";
|
|
14144
14574
|
var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
|
|
14145
14575
|
function backgroundStart(input, output) {
|
|
14146
14576
|
const flag = input && typeof input === "object" ? input.run_in_background : undefined;
|
|
@@ -14153,14 +14583,14 @@ function spawnDetached(output) {
|
|
|
14153
14583
|
return output.includes("Async agent launched successfully");
|
|
14154
14584
|
}
|
|
14155
14585
|
function claudeConfigDir() {
|
|
14156
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
14586
|
+
return process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude");
|
|
14157
14587
|
}
|
|
14158
14588
|
function sessionLogFor(file, configDir = claudeConfigDir()) {
|
|
14159
14589
|
const tasks = dirname4(file);
|
|
14160
14590
|
if (basename2(tasks) !== "tasks")
|
|
14161
14591
|
return null;
|
|
14162
14592
|
const session = dirname4(tasks);
|
|
14163
|
-
const path =
|
|
14593
|
+
const path = join8(configDir, "projects", basename2(dirname4(session)), `${basename2(session)}.jsonl`);
|
|
14164
14594
|
return existsSync8(path) ? path : null;
|
|
14165
14595
|
}
|
|
14166
14596
|
function taskEnd(text, toolId) {
|
|
@@ -14521,11 +14951,32 @@ function endOfAsk(req, res, memos) {
|
|
|
14521
14951
|
isError: !allowed
|
|
14522
14952
|
};
|
|
14523
14953
|
}
|
|
14524
|
-
function
|
|
14954
|
+
function release(toolId, memo) {
|
|
14955
|
+
const held = memo.held;
|
|
14956
|
+
if (!held)
|
|
14957
|
+
return null;
|
|
14958
|
+
delete memo.held;
|
|
14959
|
+
if (!emptyInput(memo.input))
|
|
14960
|
+
memo.writing = false;
|
|
14961
|
+
return memo.ended ? null : { type: "tool-update", toolId, ...held };
|
|
14962
|
+
}
|
|
14963
|
+
function preempted(memos) {
|
|
14964
|
+
for (const memo of memos.values()) {
|
|
14965
|
+
if (!memo.ended && memo.writing && !memo.parent)
|
|
14966
|
+
memo.cut = true;
|
|
14967
|
+
}
|
|
14968
|
+
}
|
|
14969
|
+
function abandoned(memos, parent, opening = false) {
|
|
14525
14970
|
const out = [];
|
|
14526
14971
|
for (const [toolId, memo] of memos) {
|
|
14527
14972
|
if (memo.ended || !memo.writing || memo.parent !== parent)
|
|
14528
14973
|
continue;
|
|
14974
|
+
if (opening && memo.held && !memo.cut) {
|
|
14975
|
+
const sent = release(toolId, memo);
|
|
14976
|
+
if (sent)
|
|
14977
|
+
out.push(sent);
|
|
14978
|
+
continue;
|
|
14979
|
+
}
|
|
14529
14980
|
memo.ended = true;
|
|
14530
14981
|
out.push({ type: "tool-end", toolId });
|
|
14531
14982
|
}
|
|
@@ -14554,7 +15005,7 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14554
15005
|
}
|
|
14555
15006
|
case "tool_call": {
|
|
14556
15007
|
const spawn = spawnOf(update._meta);
|
|
14557
|
-
const out = abandoned(memos, spawn.parentToolId);
|
|
15008
|
+
const out = abandoned(memos, spawn.parentToolId, true);
|
|
14558
15009
|
const input = update.rawInput ?? { locations: update.locations ?? [] };
|
|
14559
15010
|
const head = heading(update);
|
|
14560
15011
|
const memo = {
|
|
@@ -14609,26 +15060,27 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
14609
15060
|
...spawn
|
|
14610
15061
|
});
|
|
14611
15062
|
}
|
|
14612
|
-
|
|
14613
|
-
return out;
|
|
14614
|
-
const refined = { type: "tool-update", toolId: update.toolCallId };
|
|
15063
|
+
const refined = { ...memo.held };
|
|
14615
15064
|
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) {
|
|
15065
|
+
if (title && title !== memo.title)
|
|
15066
|
+
refined.title = memo.title = title;
|
|
15067
|
+
if (update.name && update.name !== memo.name)
|
|
14621
15068
|
refined.name = memo.name = update.name;
|
|
14622
|
-
}
|
|
14623
15069
|
if (update.kind && update.kind !== memo.kind)
|
|
14624
15070
|
refined.kind = memo.kind = update.kind;
|
|
14625
|
-
if (update.rawInput !== undefined)
|
|
15071
|
+
if (update.rawInput !== undefined)
|
|
14626
15072
|
refined.input = memo.input = update.rawInput;
|
|
14627
|
-
|
|
14628
|
-
|
|
15073
|
+
if (update.rawInput !== undefined && update.content === undefined && update.status === undefined) {
|
|
15074
|
+
if (out.length === 0)
|
|
15075
|
+
memo.held = refined;
|
|
15076
|
+
return out;
|
|
15077
|
+
}
|
|
15078
|
+
delete memo.held;
|
|
15079
|
+
if (!emptyInput(memo.input))
|
|
15080
|
+
memo.writing = false;
|
|
15081
|
+
if (Object.keys(refined).length > 0 && !memo.ended && out.length === 0) {
|
|
15082
|
+
out.push({ type: "tool-update", toolId: update.toolCallId, ...refined });
|
|
14629
15083
|
}
|
|
14630
|
-
if (Object.keys(refined).length > 2 && !memo.ended && out.length === 0)
|
|
14631
|
-
out.push(refined);
|
|
14632
15084
|
if (update.content)
|
|
14633
15085
|
memo.content = [...memo.content, ...update.content];
|
|
14634
15086
|
if (update.rawOutput !== undefined)
|
|
@@ -14776,6 +15228,11 @@ function steerOutcome(value) {
|
|
|
14776
15228
|
// ../daemon/src/agent/acp/session.ts
|
|
14777
15229
|
var DEFAULT_IDLE_MS = Number(process.env.TOYON_AGENT_IDLE_MS) || 5 * 60000;
|
|
14778
15230
|
var OWN_SETTLE_MS = 1e4;
|
|
15231
|
+
var INPUT_HOLD_MS = 1000;
|
|
15232
|
+
var queued = (q) => ({
|
|
15233
|
+
text: q.text,
|
|
15234
|
+
...q.attachments ? { attachments: q.attachments } : {}
|
|
15235
|
+
});
|
|
14779
15236
|
function recorded(reply) {
|
|
14780
15237
|
if (reply?.kind === "choice")
|
|
14781
15238
|
return { choiceId: reply.choiceId };
|
|
@@ -14794,6 +15251,7 @@ class AcpSession {
|
|
|
14794
15251
|
held = false;
|
|
14795
15252
|
conn = null;
|
|
14796
15253
|
connecting = null;
|
|
15254
|
+
opening = null;
|
|
14797
15255
|
live = null;
|
|
14798
15256
|
reaper = null;
|
|
14799
15257
|
own = null;
|
|
@@ -14891,7 +15349,7 @@ class AcpSession {
|
|
|
14891
15349
|
return this.queue.length > 0 || this.steered.length > 0 || this.refused !== null || this.asks.size > 0;
|
|
14892
15350
|
}
|
|
14893
15351
|
get queueItems() {
|
|
14894
|
-
return this.waiting().map(
|
|
15352
|
+
return this.waiting().map(queued);
|
|
14895
15353
|
}
|
|
14896
15354
|
waiting() {
|
|
14897
15355
|
return this.queue.filter((q) => !q.recorded);
|
|
@@ -14900,13 +15358,19 @@ class AcpSession {
|
|
|
14900
15358
|
queueChanged() {
|
|
14901
15359
|
this.onQueueChange?.();
|
|
14902
15360
|
}
|
|
14903
|
-
unqueue(index) {
|
|
15361
|
+
unqueue(index, taken) {
|
|
14904
15362
|
const item = this.waiting()[index];
|
|
14905
15363
|
if (!item)
|
|
14906
15364
|
return;
|
|
14907
15365
|
this.queue.splice(this.queue.indexOf(item), 1);
|
|
15366
|
+
taken?.(queued(item));
|
|
15367
|
+
this.letGo(item);
|
|
14908
15368
|
this.queueChanged();
|
|
14909
15369
|
}
|
|
15370
|
+
letGo(item) {
|
|
15371
|
+
if (!item.recorded)
|
|
15372
|
+
this.d.attachments.uploads.release(uploadIds(item.attachments));
|
|
15373
|
+
}
|
|
14910
15374
|
transcript() {
|
|
14911
15375
|
return this.log.entries;
|
|
14912
15376
|
}
|
|
@@ -14924,8 +15388,19 @@ class AcpSession {
|
|
|
14924
15388
|
send(text, opts = {}) {
|
|
14925
15389
|
if (this.stopped)
|
|
14926
15390
|
return log.warn(this.d.worktreeId, "send after close dropped");
|
|
14927
|
-
|
|
15391
|
+
if (!opts.asked)
|
|
15392
|
+
for (const ask of [...this.asks.values()])
|
|
15393
|
+
ask.settle("skipped");
|
|
15394
|
+
this.post(text, opts);
|
|
15395
|
+
}
|
|
15396
|
+
post(text, opts = {}) {
|
|
15397
|
+
const { context, attachments, asked } = opts;
|
|
14928
15398
|
const item = { text, context, ...attachments?.length ? { attachments } : {} };
|
|
15399
|
+
if (asked) {
|
|
15400
|
+
this.emit({ type: "fix-asked", text, ts: Date.now(), ...asked });
|
|
15401
|
+
item.recorded = { attachments: [] };
|
|
15402
|
+
}
|
|
15403
|
+
this.d.attachments.uploads.hold(uploadIds(attachments));
|
|
14929
15404
|
if ((this.running || this.own) && !this.interrupted && !this.held && this.live?.conn.steering) {
|
|
14930
15405
|
return fireAndForget(this.d.worktreeId, this.steer(item), "agent steer");
|
|
14931
15406
|
}
|
|
@@ -14980,7 +15455,10 @@ class AcpSession {
|
|
|
14980
15455
|
}
|
|
14981
15456
|
this.clearReaper();
|
|
14982
15457
|
log.warn(this.d.worktreeId, "steered message started a turn of the agent's own");
|
|
15458
|
+
return;
|
|
14983
15459
|
}
|
|
15460
|
+
if (this.live === live)
|
|
15461
|
+
preempted(live.tools);
|
|
14984
15462
|
}
|
|
14985
15463
|
unsteer(item) {
|
|
14986
15464
|
const at = this.steered.indexOf(item);
|
|
@@ -15012,6 +15490,8 @@ class AcpSession {
|
|
|
15012
15490
|
async close() {
|
|
15013
15491
|
this.stopped = true;
|
|
15014
15492
|
this.clearReaper();
|
|
15493
|
+
for (const item of this.queue)
|
|
15494
|
+
this.letGo(item);
|
|
15015
15495
|
this.queue = [];
|
|
15016
15496
|
this.steered = [];
|
|
15017
15497
|
this.queueChanged();
|
|
@@ -15135,8 +15615,8 @@ class AcpSession {
|
|
|
15135
15615
|
retry() {
|
|
15136
15616
|
const item = this.refused;
|
|
15137
15617
|
this.refused = null;
|
|
15138
|
-
if (item)
|
|
15139
|
-
this.
|
|
15618
|
+
if (item && !this.stopped)
|
|
15619
|
+
this.enqueue(item);
|
|
15140
15620
|
}
|
|
15141
15621
|
planWithheld(conn, methodId) {
|
|
15142
15622
|
return this.d.planSignIn?.() === false && conn.spec.terminalLogins?.[methodId]?.plan === true;
|
|
@@ -15159,8 +15639,12 @@ class AcpSession {
|
|
|
15159
15639
|
}
|
|
15160
15640
|
async record({ text, attachments }) {
|
|
15161
15641
|
const stored = [];
|
|
15162
|
-
|
|
15163
|
-
|
|
15642
|
+
try {
|
|
15643
|
+
for (const a of attachments ?? [])
|
|
15644
|
+
stored.push(await this.d.attachments.put(this.d.worktreeId, this.seq[a.kind]++, a));
|
|
15645
|
+
} finally {
|
|
15646
|
+
this.d.attachments.uploads.release(uploadIds(attachments));
|
|
15647
|
+
}
|
|
15164
15648
|
this.emit({
|
|
15165
15649
|
type: "user-message",
|
|
15166
15650
|
text,
|
|
@@ -15182,6 +15666,11 @@ class AcpSession {
|
|
|
15182
15666
|
}
|
|
15183
15667
|
async runTurn(item) {
|
|
15184
15668
|
item.recorded ??= await this.record(item);
|
|
15669
|
+
try {
|
|
15670
|
+
await this.d.beforeTurn?.();
|
|
15671
|
+
} catch (e) {
|
|
15672
|
+
log.warn(this.d.worktreeId, "before-turn hook failed", e);
|
|
15673
|
+
}
|
|
15185
15674
|
this.emit({ type: "turn-start", ts: Date.now() });
|
|
15186
15675
|
const live = await this.ensureLive("a turn");
|
|
15187
15676
|
await this.applyMode(live);
|
|
@@ -15273,8 +15762,18 @@ class AcpSession {
|
|
|
15273
15762
|
}
|
|
15274
15763
|
async ensureLive(why) {
|
|
15275
15764
|
const conn = await this.ensureConn(why);
|
|
15765
|
+
if (this.opening?.conn === conn)
|
|
15766
|
+
return this.opening.live;
|
|
15276
15767
|
if (this.live?.conn === conn)
|
|
15277
15768
|
return this.live;
|
|
15769
|
+
const live = this.openLive(conn).finally(() => {
|
|
15770
|
+
if (this.opening?.live === live)
|
|
15771
|
+
this.opening = null;
|
|
15772
|
+
});
|
|
15773
|
+
this.opening = { conn, live };
|
|
15774
|
+
return live;
|
|
15775
|
+
}
|
|
15776
|
+
async openLive(conn) {
|
|
15278
15777
|
const additionalDirectories = conn.bounds.gitDir ? [conn.bounds.gitDir] : [];
|
|
15279
15778
|
let sessionId = this.d.getSessionId();
|
|
15280
15779
|
let modes;
|
|
@@ -15315,7 +15814,7 @@ class AcpSession {
|
|
|
15315
15814
|
this.emit({ type: "session-info", sessionId, ...currentValues(options) });
|
|
15316
15815
|
this.learn(options);
|
|
15317
15816
|
const modeOption = modes ? null : readModeOption(configOptions);
|
|
15318
|
-
|
|
15817
|
+
const live = {
|
|
15319
15818
|
conn,
|
|
15320
15819
|
sessionId,
|
|
15321
15820
|
prefixPending: !resumed && conn.spec.systemPrompt === "prompt-prefix",
|
|
@@ -15325,13 +15824,14 @@ class AcpSession {
|
|
|
15325
15824
|
modeConfigId: modeOption?.id ?? null,
|
|
15326
15825
|
options
|
|
15327
15826
|
};
|
|
15328
|
-
|
|
15329
|
-
await this.
|
|
15330
|
-
await this.applyOption(
|
|
15827
|
+
this.live = live;
|
|
15828
|
+
await this.applyMode(live);
|
|
15829
|
+
await this.applyOption(live, "model");
|
|
15830
|
+
await this.applyOption(live, "thought_level");
|
|
15331
15831
|
const pushed = conn.commands.get(sessionId);
|
|
15332
15832
|
if (pushed)
|
|
15333
15833
|
this.setCommands(pushed);
|
|
15334
|
-
return
|
|
15834
|
+
return live;
|
|
15335
15835
|
}
|
|
15336
15836
|
mode() {
|
|
15337
15837
|
return this.d.mode?.() ?? DEFAULT_PERMISSION_MODE;
|
|
@@ -15410,6 +15910,10 @@ class AcpSession {
|
|
|
15410
15910
|
if (params.update.sessionUpdate === "config_option_update")
|
|
15411
15911
|
this.absorb(live, params.update.configOptions);
|
|
15412
15912
|
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));
|
|
15913
|
+
this.forward(live, events);
|
|
15914
|
+
this.holdInput(live, params.update);
|
|
15915
|
+
}
|
|
15916
|
+
forward(live, events) {
|
|
15413
15917
|
if (!this.running)
|
|
15414
15918
|
this.trackOwn(events);
|
|
15415
15919
|
for (const ev of events) {
|
|
@@ -15418,6 +15922,23 @@ class AcpSession {
|
|
|
15418
15922
|
this.emit(ev.type === "session-info" ? { ...ev, sessionId: live.sessionId } : ev);
|
|
15419
15923
|
}
|
|
15420
15924
|
}
|
|
15925
|
+
holdInput(live, update) {
|
|
15926
|
+
if (update.sessionUpdate !== "tool_call_update")
|
|
15927
|
+
return;
|
|
15928
|
+
const toolId = update.toolCallId;
|
|
15929
|
+
const memo = live.tools.get(toolId);
|
|
15930
|
+
const held = memo?.held;
|
|
15931
|
+
if (!memo || !held)
|
|
15932
|
+
return;
|
|
15933
|
+
const t = setTimeout(() => {
|
|
15934
|
+
if (this.live !== live || memo.held !== held || memo.cut || !(this.running || this.own))
|
|
15935
|
+
return;
|
|
15936
|
+
const sent = release(toolId, memo);
|
|
15937
|
+
if (sent)
|
|
15938
|
+
this.forward(live, [sent]);
|
|
15939
|
+
}, this.d.inputHoldMs ?? INPUT_HOLD_MS);
|
|
15940
|
+
t.unref?.();
|
|
15941
|
+
}
|
|
15421
15942
|
onPermission(params, bounds, sandboxed) {
|
|
15422
15943
|
const tools = this.live?.tools;
|
|
15423
15944
|
return Promise.resolve(this.answerPermission(params, bounds, sandboxed)).then((res) => {
|
|
@@ -15476,7 +15997,7 @@ class AcpSession {
|
|
|
15476
15997
|
async sayPlanEdited(proposed, path) {
|
|
15477
15998
|
if (!await this.d.planEdited?.(path, proposed))
|
|
15478
15999
|
return;
|
|
15479
|
-
this.
|
|
16000
|
+
this.post(`I edited the plan before approving it. Build what is in ${path}, not the plan you proposed.`);
|
|
15480
16001
|
}
|
|
15481
16002
|
onElicit(params, signal) {
|
|
15482
16003
|
const live = this.live;
|
|
@@ -15671,9 +16192,9 @@ function sameCommands(a, b) {
|
|
|
15671
16192
|
}
|
|
15672
16193
|
|
|
15673
16194
|
// ../daemon/src/agent/planDoc.ts
|
|
15674
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
15675
|
-
import { join as
|
|
15676
|
-
var PLANS_DIR =
|
|
16195
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
16196
|
+
import { join as join9 } from "path";
|
|
16197
|
+
var PLANS_DIR = join9(".toyon", "plans");
|
|
15677
16198
|
var PLAN_NAME = /^(\d+)\.md$/;
|
|
15678
16199
|
function isPlanPath(rel) {
|
|
15679
16200
|
return rel.startsWith(`${PLANS_DIR}/`) && PLAN_NAME.test(rel.slice(PLANS_DIR.length + 1));
|
|
@@ -15682,7 +16203,7 @@ function nextNumber(dir) {
|
|
|
15682
16203
|
if (!existsSync9(dir))
|
|
15683
16204
|
return 1;
|
|
15684
16205
|
let max = 0;
|
|
15685
|
-
for (const name of
|
|
16206
|
+
for (const name of readdirSync3(dir)) {
|
|
15686
16207
|
const n = Number(PLAN_NAME.exec(name)?.[1]);
|
|
15687
16208
|
if (n > max)
|
|
15688
16209
|
max = n;
|
|
@@ -15690,11 +16211,11 @@ function nextNumber(dir) {
|
|
|
15690
16211
|
return max + 1;
|
|
15691
16212
|
}
|
|
15692
16213
|
async function writePlanDoc(cwd, markdown) {
|
|
15693
|
-
const dir =
|
|
15694
|
-
const rel =
|
|
16214
|
+
const dir = join9(cwd, PLANS_DIR);
|
|
16215
|
+
const rel = join9(PLANS_DIR, `${nextNumber(dir)}.md`);
|
|
15695
16216
|
try {
|
|
15696
|
-
|
|
15697
|
-
writeFileSync4(
|
|
16217
|
+
mkdirSync6(dir, { recursive: true });
|
|
16218
|
+
writeFileSync4(join9(cwd, rel), markdown.endsWith(`
|
|
15698
16219
|
`) ? markdown : `${markdown}
|
|
15699
16220
|
`);
|
|
15700
16221
|
} catch (e) {
|
|
@@ -15705,7 +16226,7 @@ async function writePlanDoc(cwd, markdown) {
|
|
|
15705
16226
|
return rel;
|
|
15706
16227
|
}
|
|
15707
16228
|
function planEdited(cwd, rel, proposed) {
|
|
15708
|
-
const file =
|
|
16229
|
+
const file = join9(cwd, rel);
|
|
15709
16230
|
if (!existsSync9(file))
|
|
15710
16231
|
return false;
|
|
15711
16232
|
try {
|
|
@@ -17010,11 +17531,12 @@ function defaultAgent(wt, d, preview, onProcess) {
|
|
|
17010
17531
|
launch: (spec) => d.agents.command(spec),
|
|
17011
17532
|
planSignIn: () => d.managed?.planSignIn ?? true,
|
|
17012
17533
|
transcriptsDir: d.paths.transcriptsDir,
|
|
17013
|
-
attachments: d.attachments ?? new AttachmentStore(d.paths.attachmentsDir),
|
|
17534
|
+
attachments: d.attachments ?? new AttachmentStore(d.paths.attachmentsDir, new UploadStore(d.paths.uploadsDir)),
|
|
17014
17535
|
getSessionId: () => d.state.session(wt.id),
|
|
17015
17536
|
setSessionId: (id) => d.state.setSession(wt.id, id),
|
|
17016
17537
|
onEvent: (event, seq) => d.hub.emit("agent", wt.id, seq, event),
|
|
17017
17538
|
onStatus: (status) => d.hub.emit("agentStatus", wt.id, status),
|
|
17539
|
+
beforeTurn: async () => d.turnStarting?.(wt.id),
|
|
17018
17540
|
onAuth: (agentId, o) => d.accounts?.observe(agentId, o),
|
|
17019
17541
|
seedCommands: () => d.state.cachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId),
|
|
17020
17542
|
onCommandsLearned: (commands) => d.state.setCachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId, commands),
|
|
@@ -17159,6 +17681,18 @@ class RuntimeRegistry {
|
|
|
17159
17681
|
return true;
|
|
17160
17682
|
return false;
|
|
17161
17683
|
}
|
|
17684
|
+
demand() {
|
|
17685
|
+
if (this.holds.size > 0)
|
|
17686
|
+
return "working";
|
|
17687
|
+
let waiting = false;
|
|
17688
|
+
for (const rt of this.runtimes.values()) {
|
|
17689
|
+
if (rt.agent.status === "working")
|
|
17690
|
+
return "working";
|
|
17691
|
+
if (rt.agent.status === "waiting")
|
|
17692
|
+
waiting = true;
|
|
17693
|
+
}
|
|
17694
|
+
return waiting ? "waiting" : null;
|
|
17695
|
+
}
|
|
17162
17696
|
markSetup(id, on) {
|
|
17163
17697
|
if (on)
|
|
17164
17698
|
this.settingUp.add(id);
|
|
@@ -17318,9 +17852,12 @@ class RuntimeRegistry {
|
|
|
17318
17852
|
return rt;
|
|
17319
17853
|
}
|
|
17320
17854
|
async start(wt, repo) {
|
|
17321
|
-
|
|
17855
|
+
const record = this.deps.state.worktree(wt.id);
|
|
17856
|
+
if (!record)
|
|
17322
17857
|
return;
|
|
17323
17858
|
const rt = this.ensureAgent(wt);
|
|
17859
|
+
if (record.parked)
|
|
17860
|
+
return;
|
|
17324
17861
|
const run = repo.needsSetup ? EMPTY_RUN : resolveRun(repo, wt);
|
|
17325
17862
|
const leads = wt.kind !== "main" || !this.deps.mainLeads || this.deps.mainLeads(wt.repoId);
|
|
17326
17863
|
if (rt.procs) {
|
|
@@ -17719,6 +18256,11 @@ class RuntimeRegistry {
|
|
|
17719
18256
|
return rt?.procs ? previewTargetOf(rt.procs, rt.previewName) : null;
|
|
17720
18257
|
}
|
|
17721
18258
|
previewStanding(id) {
|
|
18259
|
+
const wt = this.deps.state.worktree(id);
|
|
18260
|
+
if (wt?.parked) {
|
|
18261
|
+
const repo = this.deps.state.repos.find((r) => r.id === wt.repoId);
|
|
18262
|
+
return repo && !repo.needsSetup && resolveRun(repo, wt).preview ? { status: "parked" } : null;
|
|
18263
|
+
}
|
|
17722
18264
|
if (this.settingUp.has(id))
|
|
17723
18265
|
return { status: "setup" };
|
|
17724
18266
|
const rt = this.runtimes.get(id);
|
|
@@ -17783,17 +18325,17 @@ class RuntimeRegistry {
|
|
|
17783
18325
|
}
|
|
17784
18326
|
|
|
17785
18327
|
// ../daemon/src/worktrees/naming.ts
|
|
17786
|
-
import { randomBytes } from "crypto";
|
|
18328
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
17787
18329
|
import { existsSync as existsSync11 } from "fs";
|
|
17788
|
-
import { join as
|
|
18330
|
+
import { join as join10 } from "path";
|
|
17789
18331
|
function shortId() {
|
|
17790
|
-
return
|
|
18332
|
+
return randomBytes2(5).toString("hex");
|
|
17791
18333
|
}
|
|
17792
18334
|
function freeSlot(parent) {
|
|
17793
18335
|
for (;; ) {
|
|
17794
18336
|
const id = shortId();
|
|
17795
18337
|
const dir = `wt-${id.slice(0, 4)}`;
|
|
17796
|
-
if (!existsSync11(
|
|
18338
|
+
if (!existsSync11(join10(parent, dir)))
|
|
17797
18339
|
return { id, dir };
|
|
17798
18340
|
}
|
|
17799
18341
|
}
|
|
@@ -17827,18 +18369,20 @@ function clip(text, max = CLIP) {
|
|
|
17827
18369
|
const line = text.replace(/\s+/g, " ").trim();
|
|
17828
18370
|
return line.length > max ? `${line.slice(0, max - 1)}\u2026` : line;
|
|
17829
18371
|
}
|
|
17830
|
-
function slice(asks, ts) {
|
|
17831
|
-
return { asks, reply: "", edits: 0, toolErrors: 0, newestTs: ts };
|
|
18372
|
+
function slice(asks, fixes, ts) {
|
|
18373
|
+
return { asks, fixes, reply: "", edits: 0, toolErrors: 0, newestTs: ts };
|
|
17832
18374
|
}
|
|
17833
18375
|
function turnsSince(entries, seenAt) {
|
|
17834
18376
|
const turns = [];
|
|
17835
18377
|
let pending = [];
|
|
18378
|
+
let pendingFixes = [];
|
|
17836
18379
|
let open = null;
|
|
17837
18380
|
const writes = new Set;
|
|
17838
18381
|
const refusals = new Map;
|
|
17839
18382
|
const start = (ts) => {
|
|
17840
|
-
const t = slice(pending, ts);
|
|
18383
|
+
const t = slice(pending, pendingFixes, ts);
|
|
17841
18384
|
pending = [];
|
|
18385
|
+
pendingFixes = [];
|
|
17842
18386
|
turns.push(t);
|
|
17843
18387
|
return t;
|
|
17844
18388
|
};
|
|
@@ -17846,12 +18390,16 @@ function turnsSince(entries, seenAt) {
|
|
|
17846
18390
|
if (open && "ts" in e)
|
|
17847
18391
|
open.newestTs = Math.max(open.newestTs, e.ts);
|
|
17848
18392
|
switch (e.type) {
|
|
17849
|
-
case "user-message":
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
else
|
|
17853
|
-
pending.push(e.text);
|
|
18393
|
+
case "user-message": {
|
|
18394
|
+
const into = open && open.stop === undefined && open.error === undefined && !open.auth ? open : null;
|
|
18395
|
+
(into?.asks ?? pending).push(e.text);
|
|
17854
18396
|
break;
|
|
18397
|
+
}
|
|
18398
|
+
case "fix-asked": {
|
|
18399
|
+
const into = open && open.stop === undefined && open.error === undefined && !open.auth ? open : null;
|
|
18400
|
+
(into?.fixes ?? pendingFixes).push(e.why);
|
|
18401
|
+
break;
|
|
18402
|
+
}
|
|
17855
18403
|
case "turn-start":
|
|
17856
18404
|
open = start(e.ts);
|
|
17857
18405
|
break;
|
|
@@ -18012,6 +18560,8 @@ function turnBlock(t) {
|
|
|
18012
18560
|
const lines = [];
|
|
18013
18561
|
if (t.asks.length)
|
|
18014
18562
|
lines.push(`User asked: ${clip(t.asks.join(" / "), 400)}`);
|
|
18563
|
+
if (t.fixes.length)
|
|
18564
|
+
lines.push(`Toyon, not the user, asked for a fix: ${clip(t.fixes.join(" / "), 400)}`);
|
|
18015
18565
|
if (t.reply.trim())
|
|
18016
18566
|
lines.push(`Agent ended with: ${clip(t.reply, 1000)}`);
|
|
18017
18567
|
return lines.join(`
|
|
@@ -18133,6 +18683,8 @@ function attachmentText(a) {
|
|
|
18133
18683
|
return a.text;
|
|
18134
18684
|
if (a.kind === "image")
|
|
18135
18685
|
return a.name ? `image ${a.name}` : "an image";
|
|
18686
|
+
if (a.kind === "file")
|
|
18687
|
+
return `file ${a.name}`;
|
|
18136
18688
|
return `element <${a.component ?? a.tag}>${a.text ? ` "${a.text}"` : ""}`;
|
|
18137
18689
|
}
|
|
18138
18690
|
function parseName(text) {
|
|
@@ -18207,29 +18759,29 @@ function makePlanner(agents) {
|
|
|
18207
18759
|
}
|
|
18208
18760
|
|
|
18209
18761
|
// ../daemon/src/core/assets.ts
|
|
18210
|
-
import { existsSync as existsSync12, readdirSync as
|
|
18211
|
-
import { join as
|
|
18762
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, rmSync as rmSync2, statSync as statSync4 } from "fs";
|
|
18763
|
+
import { join as join11, resolve as resolve5 } from "path";
|
|
18212
18764
|
function locateAssets(here, env = process.env) {
|
|
18213
|
-
const packaged = existsSync12(
|
|
18765
|
+
const packaged = existsSync12(join11(here, "shell", "index.html"));
|
|
18214
18766
|
return {
|
|
18215
|
-
shellDist: env.TOYON_SHELL_DIST ?? (packaged ?
|
|
18216
|
-
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ?
|
|
18767
|
+
shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join11(here, "shell") : join11(here, "../../shell/dist")),
|
|
18768
|
+
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join11(here, "bridge.js") : join11(here, "../../bridge/dist/bridge.js")),
|
|
18217
18769
|
sourceRoot: packaged ? null : resolve5(here, "../../.."),
|
|
18218
|
-
packageJson: packaged ?
|
|
18770
|
+
packageJson: packaged ? join11(here, "..", "package.json") : null
|
|
18219
18771
|
};
|
|
18220
18772
|
}
|
|
18221
18773
|
var KEEP_MS = 3 * 24 * 60 * 60 * 1000;
|
|
18222
18774
|
function pruneAssets(shellDist) {
|
|
18223
|
-
const dir =
|
|
18775
|
+
const dir = join11(shellDist, "assets");
|
|
18224
18776
|
let names;
|
|
18225
18777
|
try {
|
|
18226
|
-
names =
|
|
18778
|
+
names = readdirSync4(dir);
|
|
18227
18779
|
} catch {
|
|
18228
18780
|
return 0;
|
|
18229
18781
|
}
|
|
18230
18782
|
const found = [];
|
|
18231
18783
|
for (const name of names) {
|
|
18232
|
-
const path =
|
|
18784
|
+
const path = join11(dir, name);
|
|
18233
18785
|
try {
|
|
18234
18786
|
found.push({ path, mtime: statSync4(path).mtimeMs });
|
|
18235
18787
|
} catch {}
|
|
@@ -18242,100 +18794,15 @@ function pruneAssets(shellDist) {
|
|
|
18242
18794
|
if (newest - f.mtime <= KEEP_MS)
|
|
18243
18795
|
continue;
|
|
18244
18796
|
try {
|
|
18245
|
-
|
|
18797
|
+
rmSync2(f.path, { recursive: true, force: true });
|
|
18246
18798
|
gone++;
|
|
18247
18799
|
} catch {}
|
|
18248
18800
|
}
|
|
18249
18801
|
return gone;
|
|
18250
18802
|
}
|
|
18251
18803
|
|
|
18252
|
-
// ../daemon/src/core/
|
|
18253
|
-
import {
|
|
18254
|
-
import { homedir as homedir5 } from "os";
|
|
18255
|
-
var PROMPTS = {
|
|
18256
|
-
location: "Choose where the new project goes",
|
|
18257
|
-
open: "Choose a project folder to open"
|
|
18258
|
-
};
|
|
18259
|
-
var SCRIPT = [
|
|
18260
|
-
"on run argv",
|
|
18261
|
-
"activate",
|
|
18262
|
-
"POSIX path of (choose folder with prompt (item 1 of argv) default location (POSIX file (item 2 of argv)))",
|
|
18263
|
-
"end run"
|
|
18264
|
-
];
|
|
18265
|
-
function folderDialog() {
|
|
18266
|
-
let open = null;
|
|
18267
|
-
let child = null;
|
|
18268
|
-
let cancelled = false;
|
|
18269
|
-
const ask = async (start, purpose) => {
|
|
18270
|
-
const typed = start === "~" || start.startsWith("~/") ? `${homedir5()}${start.slice(1)}` : start;
|
|
18271
|
-
const from = typed.startsWith("/") && existsSync13(typed) ? typed : homedir5();
|
|
18272
|
-
cancelled = false;
|
|
18273
|
-
const p = Bun.spawn(["osascript", ...SCRIPT.flatMap((line) => ["-e", line]), PROMPTS[purpose], from], {
|
|
18274
|
-
stdout: "pipe",
|
|
18275
|
-
stderr: "pipe",
|
|
18276
|
-
stdin: "ignore"
|
|
18277
|
-
});
|
|
18278
|
-
child = p;
|
|
18279
|
-
const [out, err, code] = await Promise.all([
|
|
18280
|
-
new Response(p.stdout).text(),
|
|
18281
|
-
new Response(p.stderr).text(),
|
|
18282
|
-
p.exited
|
|
18283
|
-
]);
|
|
18284
|
-
child = null;
|
|
18285
|
-
if (code === 0)
|
|
18286
|
-
return out.trim().replace(/\/+$/, "") || "/";
|
|
18287
|
-
if (cancelled || err.includes("(-128)"))
|
|
18288
|
-
return null;
|
|
18289
|
-
throw new UserError(`the Finder dialog did not open: ${err.trim()}`);
|
|
18290
|
-
};
|
|
18291
|
-
return {
|
|
18292
|
-
choose(start, purpose) {
|
|
18293
|
-
if (process.platform !== "darwin") {
|
|
18294
|
-
return Promise.reject(new UserError("choosing a folder in Finder is only available on macOS"));
|
|
18295
|
-
}
|
|
18296
|
-
if (!open) {
|
|
18297
|
-
open = ask(start, purpose).finally(() => {
|
|
18298
|
-
open = null;
|
|
18299
|
-
});
|
|
18300
|
-
}
|
|
18301
|
-
return open;
|
|
18302
|
-
},
|
|
18303
|
-
cancel() {
|
|
18304
|
-
if (!child)
|
|
18305
|
-
return;
|
|
18306
|
-
cancelled = true;
|
|
18307
|
-
child.kill();
|
|
18308
|
-
}
|
|
18309
|
-
};
|
|
18310
|
-
}
|
|
18311
|
-
|
|
18312
|
-
// ../daemon/src/core/hub.ts
|
|
18313
|
-
class Hub {
|
|
18314
|
-
listeners = new Map;
|
|
18315
|
-
on(event, fn) {
|
|
18316
|
-
let set = this.listeners.get(event);
|
|
18317
|
-
if (!set) {
|
|
18318
|
-
set = new Set;
|
|
18319
|
-
this.listeners.set(event, set);
|
|
18320
|
-
}
|
|
18321
|
-
set.add(fn);
|
|
18322
|
-
return () => set?.delete(fn);
|
|
18323
|
-
}
|
|
18324
|
-
emit(event, ...args) {
|
|
18325
|
-
const set = this.listeners.get(event);
|
|
18326
|
-
if (!set)
|
|
18327
|
-
return;
|
|
18328
|
-
for (const fn of set) {
|
|
18329
|
-
try {
|
|
18330
|
-
const r = fn(...args);
|
|
18331
|
-
if (r instanceof Promise)
|
|
18332
|
-
r.catch((e) => log.error("hub", `async listener for ${String(event)} rejected`, e));
|
|
18333
|
-
} catch (e) {
|
|
18334
|
-
log.error("hub", `listener for ${String(event)} threw`, e);
|
|
18335
|
-
}
|
|
18336
|
-
}
|
|
18337
|
-
}
|
|
18338
|
-
}
|
|
18804
|
+
// ../daemon/src/core/awake.ts
|
|
18805
|
+
import { spawn as spawn3 } from "child_process";
|
|
18339
18806
|
|
|
18340
18807
|
// ../daemon/src/runtime/idle.ts
|
|
18341
18808
|
var LOCAL_SLEEP_MS = 2 * 60 * 60000;
|
|
@@ -18643,21 +19110,223 @@ function humanMs(ms) {
|
|
|
18643
19110
|
return `${Math.round(ms / 1000)} s`;
|
|
18644
19111
|
}
|
|
18645
19112
|
|
|
18646
|
-
// ../daemon/src/core/
|
|
18647
|
-
var
|
|
18648
|
-
function
|
|
18649
|
-
if (raw === "off")
|
|
19113
|
+
// ../daemon/src/core/awake.ts
|
|
19114
|
+
var WAIT_MS = 30 * 60000;
|
|
19115
|
+
function idleSleepAssertion(raw, platform = process.platform) {
|
|
19116
|
+
if (raw === "off" || platform !== "darwin")
|
|
18650
19117
|
return null;
|
|
18651
|
-
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
19118
|
+
return () => {
|
|
19119
|
+
const child = spawn3("/usr/bin/caffeinate", ["-i", "-w", String(process.pid)], { stdio: "ignore" });
|
|
19120
|
+
child.on("error", (e) => log.warn("awake", "caffeinate did not start, so the Mac may sleep mid-turn", e));
|
|
19121
|
+
child.unref();
|
|
19122
|
+
return () => {
|
|
19123
|
+
child.kill();
|
|
19124
|
+
};
|
|
19125
|
+
};
|
|
18655
19126
|
}
|
|
18656
19127
|
|
|
18657
|
-
class
|
|
19128
|
+
class KeepAwake {
|
|
18658
19129
|
d;
|
|
18659
|
-
|
|
18660
|
-
|
|
19130
|
+
release = null;
|
|
19131
|
+
waitingAt = null;
|
|
19132
|
+
remote = 0;
|
|
19133
|
+
remoteLeftAt = null;
|
|
19134
|
+
timer = null;
|
|
19135
|
+
gone = false;
|
|
19136
|
+
waitMs;
|
|
19137
|
+
now;
|
|
19138
|
+
constructor(d) {
|
|
19139
|
+
this.d = d;
|
|
19140
|
+
this.waitMs = d.waitMs ?? WAIT_MS;
|
|
19141
|
+
this.now = d.now ?? Date.now;
|
|
19142
|
+
d.hub.on("agentStatus", (_id, status) => {
|
|
19143
|
+
if (status === "waiting")
|
|
19144
|
+
this.waitingAt = this.now();
|
|
19145
|
+
this.reconsider();
|
|
19146
|
+
});
|
|
19147
|
+
d.hub.on("holdsChanged", () => this.reconsider());
|
|
19148
|
+
d.hub.on("keepAwakeChanged", () => this.reconsider());
|
|
19149
|
+
this.reconsider();
|
|
19150
|
+
}
|
|
19151
|
+
setting() {
|
|
19152
|
+
return this.d.assert ? this.d.mode() : null;
|
|
19153
|
+
}
|
|
19154
|
+
remoteShells(n) {
|
|
19155
|
+
if (n === this.remote)
|
|
19156
|
+
return;
|
|
19157
|
+
if (n === 0)
|
|
19158
|
+
this.remoteLeftAt = this.now();
|
|
19159
|
+
this.remote = n;
|
|
19160
|
+
this.reconsider();
|
|
19161
|
+
}
|
|
19162
|
+
stop() {
|
|
19163
|
+
this.gone = true;
|
|
19164
|
+
this.disarm();
|
|
19165
|
+
this.hold(false);
|
|
19166
|
+
}
|
|
19167
|
+
reconsider() {
|
|
19168
|
+
if (this.gone || !this.d.assert)
|
|
19169
|
+
return;
|
|
19170
|
+
this.disarm();
|
|
19171
|
+
const mode = this.d.mode();
|
|
19172
|
+
const demand = this.d.demand();
|
|
19173
|
+
if (demand !== "waiting" || !this.d.answerable)
|
|
19174
|
+
this.waitingAt = null;
|
|
19175
|
+
else
|
|
19176
|
+
this.waitingAt ??= this.now();
|
|
19177
|
+
if (mode === "off") {
|
|
19178
|
+
this.hold(false);
|
|
19179
|
+
return;
|
|
19180
|
+
}
|
|
19181
|
+
if (mode === "always" || demand === "working" || this.remote > 0) {
|
|
19182
|
+
this.hold(true);
|
|
19183
|
+
return;
|
|
19184
|
+
}
|
|
19185
|
+
const since = Math.max(this.waitingAt ?? Number.NEGATIVE_INFINITY, this.remoteLeftAt ?? Number.NEGATIVE_INFINITY);
|
|
19186
|
+
const left = since + this.waitMs - this.now();
|
|
19187
|
+
if (left > 0) {
|
|
19188
|
+
this.hold(true);
|
|
19189
|
+
this.arm(left);
|
|
19190
|
+
return;
|
|
19191
|
+
}
|
|
19192
|
+
if (this.release && Number.isFinite(since)) {
|
|
19193
|
+
log.info("awake", `nobody has answered or connected for ${humanMs(this.waitMs)}; the machine may sleep again`);
|
|
19194
|
+
}
|
|
19195
|
+
this.hold(false);
|
|
19196
|
+
}
|
|
19197
|
+
hold(on) {
|
|
19198
|
+
if (on === (this.release !== null))
|
|
19199
|
+
return;
|
|
19200
|
+
if (on) {
|
|
19201
|
+
this.release = this.d.assert?.() ?? null;
|
|
19202
|
+
} else {
|
|
19203
|
+
this.release?.();
|
|
19204
|
+
this.release = null;
|
|
19205
|
+
}
|
|
19206
|
+
log.debug("awake", on ? "holding off idle sleep" : "idle sleep allowed again");
|
|
19207
|
+
}
|
|
19208
|
+
arm(ms) {
|
|
19209
|
+
const set = this.d.setTimer ?? ((fn, delay) => {
|
|
19210
|
+
const t = setTimeout(fn, delay);
|
|
19211
|
+
t.unref?.();
|
|
19212
|
+
return t;
|
|
19213
|
+
});
|
|
19214
|
+
this.timer = set(() => {
|
|
19215
|
+
this.timer = null;
|
|
19216
|
+
this.reconsider();
|
|
19217
|
+
}, ms);
|
|
19218
|
+
}
|
|
19219
|
+
disarm() {
|
|
19220
|
+
if (!this.timer)
|
|
19221
|
+
return;
|
|
19222
|
+
(this.d.clearTimer ?? ((t) => clearTimeout(t)))(this.timer);
|
|
19223
|
+
this.timer = null;
|
|
19224
|
+
}
|
|
19225
|
+
}
|
|
19226
|
+
|
|
19227
|
+
// ../daemon/src/core/dialog.ts
|
|
19228
|
+
import { existsSync as existsSync13 } from "fs";
|
|
19229
|
+
import { homedir as homedir5 } from "os";
|
|
19230
|
+
var PROMPTS = {
|
|
19231
|
+
location: "Choose where the new project goes",
|
|
19232
|
+
open: "Choose a project folder to open"
|
|
19233
|
+
};
|
|
19234
|
+
var SCRIPT = [
|
|
19235
|
+
"on run argv",
|
|
19236
|
+
"activate",
|
|
19237
|
+
"POSIX path of (choose folder with prompt (item 1 of argv) default location (POSIX file (item 2 of argv)))",
|
|
19238
|
+
"end run"
|
|
19239
|
+
];
|
|
19240
|
+
function folderDialog() {
|
|
19241
|
+
let open = null;
|
|
19242
|
+
let child = null;
|
|
19243
|
+
let cancelled = false;
|
|
19244
|
+
const ask = async (start, purpose) => {
|
|
19245
|
+
const typed = start === "~" || start.startsWith("~/") ? `${homedir5()}${start.slice(1)}` : start;
|
|
19246
|
+
const from = typed.startsWith("/") && existsSync13(typed) ? typed : homedir5();
|
|
19247
|
+
cancelled = false;
|
|
19248
|
+
const p = Bun.spawn(["osascript", ...SCRIPT.flatMap((line) => ["-e", line]), PROMPTS[purpose], from], {
|
|
19249
|
+
stdout: "pipe",
|
|
19250
|
+
stderr: "pipe",
|
|
19251
|
+
stdin: "ignore"
|
|
19252
|
+
});
|
|
19253
|
+
child = p;
|
|
19254
|
+
const [out, err, code] = await Promise.all([
|
|
19255
|
+
new Response(p.stdout).text(),
|
|
19256
|
+
new Response(p.stderr).text(),
|
|
19257
|
+
p.exited
|
|
19258
|
+
]);
|
|
19259
|
+
child = null;
|
|
19260
|
+
if (code === 0)
|
|
19261
|
+
return out.trim().replace(/\/+$/, "") || "/";
|
|
19262
|
+
if (cancelled || err.includes("(-128)"))
|
|
19263
|
+
return null;
|
|
19264
|
+
throw new UserError(`the Finder dialog did not open: ${err.trim()}`);
|
|
19265
|
+
};
|
|
19266
|
+
return {
|
|
19267
|
+
choose(start, purpose) {
|
|
19268
|
+
if (process.platform !== "darwin") {
|
|
19269
|
+
return Promise.reject(new UserError("choosing a folder in Finder is only available on macOS"));
|
|
19270
|
+
}
|
|
19271
|
+
if (!open) {
|
|
19272
|
+
open = ask(start, purpose).finally(() => {
|
|
19273
|
+
open = null;
|
|
19274
|
+
});
|
|
19275
|
+
}
|
|
19276
|
+
return open;
|
|
19277
|
+
},
|
|
19278
|
+
cancel() {
|
|
19279
|
+
if (!child)
|
|
19280
|
+
return;
|
|
19281
|
+
cancelled = true;
|
|
19282
|
+
child.kill();
|
|
19283
|
+
}
|
|
19284
|
+
};
|
|
19285
|
+
}
|
|
19286
|
+
|
|
19287
|
+
// ../daemon/src/core/hub.ts
|
|
19288
|
+
class Hub {
|
|
19289
|
+
listeners = new Map;
|
|
19290
|
+
on(event, fn) {
|
|
19291
|
+
let set = this.listeners.get(event);
|
|
19292
|
+
if (!set) {
|
|
19293
|
+
set = new Set;
|
|
19294
|
+
this.listeners.set(event, set);
|
|
19295
|
+
}
|
|
19296
|
+
set.add(fn);
|
|
19297
|
+
return () => set?.delete(fn);
|
|
19298
|
+
}
|
|
19299
|
+
emit(event, ...args) {
|
|
19300
|
+
const set = this.listeners.get(event);
|
|
19301
|
+
if (!set)
|
|
19302
|
+
return;
|
|
19303
|
+
for (const fn of set) {
|
|
19304
|
+
try {
|
|
19305
|
+
const r = fn(...args);
|
|
19306
|
+
if (r instanceof Promise)
|
|
19307
|
+
r.catch((e) => log.error("hub", `async listener for ${String(event)} rejected`, e));
|
|
19308
|
+
} catch (e) {
|
|
19309
|
+
log.error("hub", `listener for ${String(event)} threw`, e);
|
|
19310
|
+
}
|
|
19311
|
+
}
|
|
19312
|
+
}
|
|
19313
|
+
}
|
|
19314
|
+
|
|
19315
|
+
// ../daemon/src/core/idleExit.ts
|
|
19316
|
+
var STOP_AFTER_MS = 30 * 60000;
|
|
19317
|
+
function stopAfterFrom(raw, opts) {
|
|
19318
|
+
if (raw === "off")
|
|
19319
|
+
return null;
|
|
19320
|
+
const n = Number(raw);
|
|
19321
|
+
if (raw && Number.isFinite(n) && n > 0)
|
|
19322
|
+
return n;
|
|
19323
|
+
return opts.remote || opts.terminal || (opts.inCloud ?? cloud.enabled) ? null : STOP_AFTER_MS;
|
|
19324
|
+
}
|
|
19325
|
+
|
|
19326
|
+
class IdleExit {
|
|
19327
|
+
d;
|
|
19328
|
+
connected = 0;
|
|
19329
|
+
quietAt;
|
|
18661
19330
|
timer = null;
|
|
18662
19331
|
gone = false;
|
|
18663
19332
|
afterMs;
|
|
@@ -18732,7 +19401,7 @@ class IdleExit {
|
|
|
18732
19401
|
}
|
|
18733
19402
|
|
|
18734
19403
|
// ../daemon/src/core/restart.ts
|
|
18735
|
-
import { spawn as
|
|
19404
|
+
import { spawn as spawn4 } from "child_process";
|
|
18736
19405
|
import { openSync } from "fs";
|
|
18737
19406
|
import { homedir as homedir6 } from "os";
|
|
18738
19407
|
function restartable(env = process.env) {
|
|
@@ -18753,7 +19422,7 @@ async function replacementRuns() {
|
|
|
18753
19422
|
}
|
|
18754
19423
|
function respawn(logFile) {
|
|
18755
19424
|
const logFd = openSync(logFile, "a");
|
|
18756
|
-
const child =
|
|
19425
|
+
const child = spawn4(process.execPath, process.argv.slice(1), {
|
|
18757
19426
|
detached: true,
|
|
18758
19427
|
stdio: ["ignore", logFd, logFd],
|
|
18759
19428
|
env: { ...process.env }
|
|
@@ -18894,7 +19563,7 @@ class SelfWatch {
|
|
|
18894
19563
|
}
|
|
18895
19564
|
|
|
18896
19565
|
// ../daemon/src/core/state.ts
|
|
18897
|
-
import { randomBytes as
|
|
19566
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
18898
19567
|
import { existsSync as existsSync14, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
18899
19568
|
var GROUPS_SAVE_MS = 250;
|
|
18900
19569
|
var SEEN_WORKTREES = 50;
|
|
@@ -18956,7 +19625,7 @@ function loadOrCreateToken(paths) {
|
|
|
18956
19625
|
const TOKEN_FILE = paths.tokenFile;
|
|
18957
19626
|
if (existsSync14(TOKEN_FILE))
|
|
18958
19627
|
return readFileSync10(TOKEN_FILE, "utf8").trim();
|
|
18959
|
-
const token =
|
|
19628
|
+
const token = randomBytes3(32).toString("hex");
|
|
18960
19629
|
writeFileSync5(TOKEN_FILE, token, { mode: 384 });
|
|
18961
19630
|
return token;
|
|
18962
19631
|
}
|
|
@@ -19138,6 +19807,16 @@ class StateStore {
|
|
|
19138
19807
|
delete this.state.defaultAgent;
|
|
19139
19808
|
this.save();
|
|
19140
19809
|
}
|
|
19810
|
+
get keepAwake() {
|
|
19811
|
+
return this.state.keepAwake ?? "use";
|
|
19812
|
+
}
|
|
19813
|
+
setKeepAwake(mode) {
|
|
19814
|
+
if (mode === "use")
|
|
19815
|
+
delete this.state.keepAwake;
|
|
19816
|
+
else
|
|
19817
|
+
this.state.keepAwake = mode;
|
|
19818
|
+
this.save();
|
|
19819
|
+
}
|
|
19141
19820
|
get theme() {
|
|
19142
19821
|
return this.state.theme;
|
|
19143
19822
|
}
|
|
@@ -19170,7 +19849,7 @@ class StateStore {
|
|
|
19170
19849
|
}
|
|
19171
19850
|
|
|
19172
19851
|
// ../daemon/src/design/service.ts
|
|
19173
|
-
import { join as
|
|
19852
|
+
import { join as join12 } from "path";
|
|
19174
19853
|
|
|
19175
19854
|
// ../daemon/src/design/scan.ts
|
|
19176
19855
|
var COLOR = /^(#|rgba?\(|hsla?\(|oklch\(|oklab\(|lab\(|lch\(|color\(|color-mix\(|light-dark\()/i;
|
|
@@ -19620,7 +20299,7 @@ class DesignService {
|
|
|
19620
20299
|
async function readAll(root, paths) {
|
|
19621
20300
|
const out = [];
|
|
19622
20301
|
for (const path of paths) {
|
|
19623
|
-
const file = Bun.file(
|
|
20302
|
+
const file = Bun.file(join12(root, path));
|
|
19624
20303
|
if (file.size === 0 || file.size > MAX_FILE_BYTES)
|
|
19625
20304
|
continue;
|
|
19626
20305
|
try {
|
|
@@ -19735,7 +20414,7 @@ function typicalUse(classes) {
|
|
|
19735
20414
|
var normal = (s) => s.replace(/[-_]/g, "").toLowerCase();
|
|
19736
20415
|
async function loadProjectTypescript(root) {
|
|
19737
20416
|
try {
|
|
19738
|
-
return await import(
|
|
20417
|
+
return await import(join12(root, "node_modules", "typescript", "lib", "typescript.js"));
|
|
19739
20418
|
} catch {
|
|
19740
20419
|
return null;
|
|
19741
20420
|
}
|
|
@@ -19748,28 +20427,32 @@ var SAVE_DELAY_MS = 1000;
|
|
|
19748
20427
|
class DraftStore {
|
|
19749
20428
|
d;
|
|
19750
20429
|
drafts = {};
|
|
20430
|
+
lists = {};
|
|
20431
|
+
dirty = { text: false, lists: false };
|
|
19751
20432
|
saveTimer = null;
|
|
19752
20433
|
constructor(d) {
|
|
19753
20434
|
this.d = d;
|
|
19754
|
-
|
|
19755
|
-
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
|
|
19761
|
-
|
|
19762
|
-
|
|
19763
|
-
|
|
19764
|
-
|
|
19765
|
-
log.warn("drafts", `skipped an unreadable ${d.file}`, e);
|
|
20435
|
+
for (const [id, text] of readRecord(d.file)) {
|
|
20436
|
+
if (typeof text === "string" && text)
|
|
20437
|
+
this.drafts[id] = text;
|
|
20438
|
+
}
|
|
20439
|
+
for (const [id, list] of readRecord(d.attachmentsFile)) {
|
|
20440
|
+
const items = (Array.isArray(list) ? list : []).flatMap((item) => {
|
|
20441
|
+
const r = attachmentInputSchema.safeParse(item);
|
|
20442
|
+
return r.success ? [r.data] : [];
|
|
20443
|
+
});
|
|
20444
|
+
if (items.length)
|
|
20445
|
+
this.lists[id] = items;
|
|
19766
20446
|
}
|
|
19767
20447
|
}
|
|
19768
20448
|
all() {
|
|
19769
20449
|
return { ...this.drafts };
|
|
19770
20450
|
}
|
|
20451
|
+
allAttachments() {
|
|
20452
|
+
return { ...this.lists };
|
|
20453
|
+
}
|
|
19771
20454
|
has(boxId) {
|
|
19772
|
-
return !!this.drafts[boxId]?.trim();
|
|
20455
|
+
return !!this.drafts[boxId]?.trim() || !!this.lists[boxId]?.length;
|
|
19773
20456
|
}
|
|
19774
20457
|
text(boxId) {
|
|
19775
20458
|
return this.drafts[boxId] ?? "";
|
|
@@ -19781,22 +20464,67 @@ class DraftStore {
|
|
|
19781
20464
|
this.drafts[boxId] = text;
|
|
19782
20465
|
else
|
|
19783
20466
|
delete this.drafts[boxId];
|
|
19784
|
-
this.scheduleSave();
|
|
20467
|
+
this.scheduleSave("text");
|
|
19785
20468
|
this.d.hub.emit("draftChanged", boxId, text, clientId);
|
|
19786
20469
|
}
|
|
20470
|
+
attachments(boxId) {
|
|
20471
|
+
return this.lists[boxId] ?? [];
|
|
20472
|
+
}
|
|
20473
|
+
setAttachments(boxId, items, clientId) {
|
|
20474
|
+
if (JSON.stringify(this.lists[boxId] ?? []) === JSON.stringify(items))
|
|
20475
|
+
return;
|
|
20476
|
+
if (items.length)
|
|
20477
|
+
this.lists[boxId] = items;
|
|
20478
|
+
else
|
|
20479
|
+
delete this.lists[boxId];
|
|
20480
|
+
this.scheduleSave("lists");
|
|
20481
|
+
this.d.uploads?.keep(this.uploadIds());
|
|
20482
|
+
this.d.hub.emit("attachmentsChanged", boxId, items, clientId);
|
|
20483
|
+
}
|
|
20484
|
+
take(boxId, clientId) {
|
|
20485
|
+
const was = { text: this.text(boxId), items: this.attachments(boxId) };
|
|
20486
|
+
this.set(boxId, "", clientId);
|
|
20487
|
+
this.setAttachments(boxId, [], clientId);
|
|
20488
|
+
return was;
|
|
20489
|
+
}
|
|
20490
|
+
putBack(boxId, was, clientId) {
|
|
20491
|
+
if (was.text && !this.text(boxId))
|
|
20492
|
+
this.set(boxId, was.text, clientId);
|
|
20493
|
+
if (was.items.length)
|
|
20494
|
+
this.setAttachments(boxId, [...was.items, ...this.attachments(boxId)], clientId);
|
|
20495
|
+
}
|
|
20496
|
+
uploadIds() {
|
|
20497
|
+
return Object.values(this.lists).flatMap((items) => uploadIds(items));
|
|
20498
|
+
}
|
|
19787
20499
|
drop(boxId) {
|
|
19788
20500
|
this.set(boxId, "");
|
|
20501
|
+
this.setAttachments(boxId, []);
|
|
20502
|
+
}
|
|
20503
|
+
move(fromId, toId, over = false) {
|
|
20504
|
+
const text = this.text(fromId);
|
|
20505
|
+
const items = this.attachments(fromId);
|
|
20506
|
+
if (!text && !items.length)
|
|
20507
|
+
return false;
|
|
20508
|
+
if (!over && (this.text(toId) || this.attachments(toId).length))
|
|
20509
|
+
return false;
|
|
20510
|
+
this.set(toId, text);
|
|
20511
|
+
this.setAttachments(toId, items);
|
|
20512
|
+
this.drop(fromId);
|
|
20513
|
+
return true;
|
|
19789
20514
|
}
|
|
19790
20515
|
prune(known) {
|
|
19791
|
-
let changed = false;
|
|
19792
20516
|
for (const id of Object.keys(this.drafts)) {
|
|
19793
20517
|
if (known(id))
|
|
19794
20518
|
continue;
|
|
19795
20519
|
delete this.drafts[id];
|
|
19796
|
-
|
|
20520
|
+
this.scheduleSave("text");
|
|
20521
|
+
}
|
|
20522
|
+
for (const id of Object.keys(this.lists)) {
|
|
20523
|
+
if (known(id))
|
|
20524
|
+
continue;
|
|
20525
|
+
delete this.lists[id];
|
|
20526
|
+
this.scheduleSave("lists");
|
|
19797
20527
|
}
|
|
19798
|
-
if (changed)
|
|
19799
|
-
this.scheduleSave();
|
|
19800
20528
|
}
|
|
19801
20529
|
flush() {
|
|
19802
20530
|
if (!this.saveTimer)
|
|
@@ -19805,7 +20533,8 @@ class DraftStore {
|
|
|
19805
20533
|
this.saveTimer = null;
|
|
19806
20534
|
this.write();
|
|
19807
20535
|
}
|
|
19808
|
-
scheduleSave() {
|
|
20536
|
+
scheduleSave(which) {
|
|
20537
|
+
this.dirty[which] = true;
|
|
19809
20538
|
if (this.saveTimer)
|
|
19810
20539
|
return;
|
|
19811
20540
|
this.saveTimer = setTimeout(() => {
|
|
@@ -19815,13 +20544,31 @@ class DraftStore {
|
|
|
19815
20544
|
this.saveTimer.unref?.();
|
|
19816
20545
|
}
|
|
19817
20546
|
write() {
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
20547
|
+
if (this.dirty.text)
|
|
20548
|
+
writeAtomic(this.d.file, this.drafts);
|
|
20549
|
+
if (this.dirty.lists)
|
|
20550
|
+
writeAtomic(this.d.attachmentsFile, this.lists);
|
|
20551
|
+
this.dirty = { text: false, lists: false };
|
|
20552
|
+
}
|
|
20553
|
+
}
|
|
20554
|
+
function readRecord(file) {
|
|
20555
|
+
if (!existsSync15(file))
|
|
20556
|
+
return [];
|
|
20557
|
+
try {
|
|
20558
|
+
const raw = JSON.parse(readFileSync11(file, "utf8"));
|
|
20559
|
+
return raw && typeof raw === "object" ? Object.entries(raw) : [];
|
|
20560
|
+
} catch (e) {
|
|
20561
|
+
log.warn("drafts", `skipped an unreadable ${file}`, e);
|
|
20562
|
+
return [];
|
|
20563
|
+
}
|
|
20564
|
+
}
|
|
20565
|
+
function writeAtomic(file, value) {
|
|
20566
|
+
const tmp = `${file}.tmp`;
|
|
20567
|
+
try {
|
|
20568
|
+
writeFileSync6(tmp, JSON.stringify(value));
|
|
20569
|
+
renameSync3(tmp, file);
|
|
20570
|
+
} catch (e) {
|
|
20571
|
+
log.error("drafts", `could not write ${file}`, e);
|
|
19825
20572
|
}
|
|
19826
20573
|
}
|
|
19827
20574
|
|
|
@@ -19879,12 +20626,13 @@ class ExecService {
|
|
|
19879
20626
|
if (opts.quiet)
|
|
19880
20627
|
agent.note(start);
|
|
19881
20628
|
agent.note({ type: "tool-end", toolId, output: `could not run: ${reason}`, isError: true });
|
|
19882
|
-
return Promise.resolve({ exit: reason, text: "" });
|
|
20629
|
+
return Promise.resolve({ exit: reason, text: "", toolId });
|
|
19883
20630
|
}
|
|
19884
20631
|
const pid = proc.pid;
|
|
19885
20632
|
let killing;
|
|
19886
20633
|
const running = {
|
|
19887
20634
|
stop: () => {
|
|
20635
|
+
running.stopping = true;
|
|
19888
20636
|
killing ??= killGroup(pid, groupGone(pid)).then((signal) => {
|
|
19889
20637
|
if (signal)
|
|
19890
20638
|
running.signal = signal;
|
|
@@ -19901,7 +20649,8 @@ class ExecService {
|
|
|
19901
20649
|
this.deps.runtime.hold(worktreeId, `exec:${toolId}`);
|
|
19902
20650
|
this.deps.runtime.noteGroup(worktreeId, `exec:${toolId}`, pid);
|
|
19903
20651
|
opts.onSpawn?.(pid, () => fireAndForget(worktreeId, running.stop(), `exec ${toolId} stopped`));
|
|
19904
|
-
|
|
20652
|
+
const kind = name === CHECK_TOOL ? "check" : "command";
|
|
20653
|
+
return this.collect(worktreeId, toolId, proc, running, agent, ceiling, kind, opts.quiet ? start : undefined);
|
|
19905
20654
|
}
|
|
19906
20655
|
async watch(worktreeId, command, run) {
|
|
19907
20656
|
const agent = this.agentFor(worktreeId);
|
|
@@ -19939,16 +20688,17 @@ class ExecService {
|
|
|
19939
20688
|
this.untrack(worktreeId, toolId);
|
|
19940
20689
|
}
|
|
19941
20690
|
if (!up && r.exit === 0)
|
|
19942
|
-
return { ...r, shown: false };
|
|
20691
|
+
return { ...r, shown: false, toolId };
|
|
19943
20692
|
if (!up)
|
|
19944
20693
|
agent.note(start);
|
|
19945
20694
|
agent.note({
|
|
19946
20695
|
type: "tool-end",
|
|
19947
20696
|
toolId,
|
|
19948
20697
|
output: formatOutput(text, r.exit, truncated, r.ceilingMs),
|
|
19949
|
-
isError: r.exit !== 0
|
|
20698
|
+
isError: r.exit !== 0,
|
|
20699
|
+
...r.hook && typeof r.exit === "number" && r.exit !== 0 ? { fixable: { kind: "hook", hook: r.hook } } : {}
|
|
19950
20700
|
});
|
|
19951
|
-
return { ...r, shown: true };
|
|
20701
|
+
return { ...r, shown: true, toolId };
|
|
19952
20702
|
}
|
|
19953
20703
|
async stop(worktreeId, toolId) {
|
|
19954
20704
|
const byTool = this.running.get(worktreeId);
|
|
@@ -19981,7 +20731,7 @@ class ExecService {
|
|
|
19981
20731
|
this.deps.runtime.release(worktreeId, `exec:${toolId}`);
|
|
19982
20732
|
this.deps.runtime.forgetGroup(worktreeId, pgid);
|
|
19983
20733
|
}
|
|
19984
|
-
async collect(worktreeId, toolId, proc, running, agent, ceiling, heldStart) {
|
|
20734
|
+
async collect(worktreeId, toolId, proc, running, agent, ceiling, kind, heldStart) {
|
|
19985
20735
|
let text = "";
|
|
19986
20736
|
let truncated = false;
|
|
19987
20737
|
const live = !heldStart;
|
|
@@ -20019,7 +20769,13 @@ class ExecService {
|
|
|
20019
20769
|
log.warn(worktreeId, "exec: reading output failed", e);
|
|
20020
20770
|
}
|
|
20021
20771
|
const pgid = proc.pid;
|
|
20022
|
-
const end = (how) => agent.note({
|
|
20772
|
+
const end = (how) => agent.note({
|
|
20773
|
+
type: "tool-end",
|
|
20774
|
+
toolId,
|
|
20775
|
+
output: formatOutput(text, how, truncated, ceiling),
|
|
20776
|
+
isError: how !== 0,
|
|
20777
|
+
...typeof how === "number" && how !== 0 && !running.stopping && !running.timedOut ? { fixable: { kind } } : {}
|
|
20778
|
+
});
|
|
20023
20779
|
const lingers = running.signal === undefined && groupAlive(pgid);
|
|
20024
20780
|
if (!lingers)
|
|
20025
20781
|
this.settle(worktreeId, toolId, pgid);
|
|
@@ -20030,18 +20786,18 @@ class ExecService {
|
|
|
20030
20786
|
}
|
|
20031
20787
|
if (lingers)
|
|
20032
20788
|
fireAndForget(worktreeId, this.linger(running, pgid).then(() => this.settle(worktreeId, toolId, pgid)), `exec ${toolId} left running`);
|
|
20033
|
-
return { exit: running.timedOut ? "timeout" : exit, text };
|
|
20789
|
+
return { exit: running.timedOut ? "timeout" : exit, text, toolId };
|
|
20034
20790
|
}
|
|
20035
20791
|
if (!lingers) {
|
|
20036
20792
|
end(running.timedOut ? "timeout" : exit);
|
|
20037
|
-
return { exit: running.timedOut ? "timeout" : exit, text };
|
|
20793
|
+
return { exit: running.timedOut ? "timeout" : exit, text, toolId };
|
|
20038
20794
|
}
|
|
20039
20795
|
agent.note({ type: "tool-update", toolId, background: true });
|
|
20040
20796
|
fireAndForget(worktreeId, this.linger(running, pgid).then(() => {
|
|
20041
20797
|
this.settle(worktreeId, toolId, pgid);
|
|
20042
20798
|
end(running.timedOut ? "timeout" : running.signal ?? exit);
|
|
20043
20799
|
}), `exec ${toolId} in the background`);
|
|
20044
|
-
return { exit, text };
|
|
20800
|
+
return { exit, text, toolId };
|
|
20045
20801
|
}
|
|
20046
20802
|
async linger(running, pgid) {
|
|
20047
20803
|
while (running.signal === undefined && groupAlive(pgid))
|
|
@@ -20050,17 +20806,17 @@ class ExecService {
|
|
|
20050
20806
|
}
|
|
20051
20807
|
|
|
20052
20808
|
// ../daemon/src/files/open.ts
|
|
20053
|
-
import { stat as
|
|
20809
|
+
import { stat as stat3 } from "fs/promises";
|
|
20054
20810
|
import { basename as basename3, resolve as resolve7 } from "path";
|
|
20055
20811
|
|
|
20056
20812
|
// ../daemon/src/repos/browse.ts
|
|
20057
20813
|
import { existsSync as existsSync16, statSync as statSync5 } from "fs";
|
|
20058
20814
|
import { readdir } from "fs/promises";
|
|
20059
20815
|
import { homedir as homedir7 } from "os";
|
|
20060
|
-
import { dirname as dirname5, join as
|
|
20816
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
20061
20817
|
var MAX_ENTRIES = 40;
|
|
20062
20818
|
function expandTilde(raw) {
|
|
20063
|
-
return raw === "~" || raw.startsWith("~/") ?
|
|
20819
|
+
return raw === "~" || raw.startsWith("~/") ? join13(homedir7(), raw.slice(1)) : raw;
|
|
20064
20820
|
}
|
|
20065
20821
|
function collapseTilde(abs) {
|
|
20066
20822
|
const home = homedir7();
|
|
@@ -20071,7 +20827,7 @@ function holdsNothing(names) {
|
|
|
20071
20827
|
}
|
|
20072
20828
|
async function describeFolder(abs) {
|
|
20073
20829
|
const path = collapseTilde(abs);
|
|
20074
|
-
if (existsSync16(
|
|
20830
|
+
if (existsSync16(join13(abs, ".git")))
|
|
20075
20831
|
return { path, kind: "project" };
|
|
20076
20832
|
try {
|
|
20077
20833
|
return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
|
|
@@ -20088,7 +20844,7 @@ function describe2(typed, parent) {
|
|
|
20088
20844
|
return {
|
|
20089
20845
|
exists: existsSync16(typed),
|
|
20090
20846
|
isDir,
|
|
20091
|
-
isRepo: isDir && existsSync16(
|
|
20847
|
+
isRepo: isDir && existsSync16(join13(typed, ".git")),
|
|
20092
20848
|
parentExists: existsSync16(parent)
|
|
20093
20849
|
};
|
|
20094
20850
|
}
|
|
@@ -20113,8 +20869,8 @@ async function browsePath(raw) {
|
|
|
20113
20869
|
continue;
|
|
20114
20870
|
if (!name.toLowerCase().startsWith(prefix))
|
|
20115
20871
|
continue;
|
|
20116
|
-
const abs =
|
|
20117
|
-
entries.push({ path: collapseTilde(abs), name, isRepo: existsSync16(
|
|
20872
|
+
const abs = join13(dir, name);
|
|
20873
|
+
entries.push({ path: collapseTilde(abs), name, isRepo: existsSync16(join13(abs, ".git")) });
|
|
20118
20874
|
}
|
|
20119
20875
|
entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
|
|
20120
20876
|
return { entries: entries.slice(0, MAX_ENTRIES), target };
|
|
@@ -20150,8 +20906,8 @@ function looksBinary(text) {
|
|
|
20150
20906
|
}
|
|
20151
20907
|
|
|
20152
20908
|
// ../daemon/src/files/service.ts
|
|
20153
|
-
import { spawn as
|
|
20154
|
-
import { stat, unlink } from "fs/promises";
|
|
20909
|
+
import { spawn as spawn5 } from "child_process";
|
|
20910
|
+
import { stat as stat2, unlink } from "fs/promises";
|
|
20155
20911
|
|
|
20156
20912
|
// ../daemon/src/git/blame.ts
|
|
20157
20913
|
var NONE = { commits: [], lines: [] };
|
|
@@ -20222,10 +20978,13 @@ async function withLock(key, fn) {
|
|
|
20222
20978
|
function withRepoLock(repoPath, fn) {
|
|
20223
20979
|
return withLock(repoPath, fn);
|
|
20224
20980
|
}
|
|
20981
|
+
function withFetchLock(repoPath, fn) {
|
|
20982
|
+
return withLock(`fetch:${repoPath}`, fn);
|
|
20983
|
+
}
|
|
20225
20984
|
var fileLockKey = (absPath) => `file:${absPath}`;
|
|
20226
20985
|
|
|
20227
20986
|
// ../daemon/src/git/status.ts
|
|
20228
|
-
import { join as
|
|
20987
|
+
import { join as join14 } from "path";
|
|
20229
20988
|
async function treeFingerprint(worktreePath) {
|
|
20230
20989
|
const [head, diff, untracked] = await Promise.all([
|
|
20231
20990
|
git(worktreePath, "rev-parse", "HEAD"),
|
|
@@ -20235,7 +20994,7 @@ async function treeFingerprint(worktreePath) {
|
|
|
20235
20994
|
const others = await Promise.all(untracked.out.split(`
|
|
20236
20995
|
`).filter(Boolean).map(async (p) => {
|
|
20237
20996
|
try {
|
|
20238
|
-
const f = Bun.file(
|
|
20997
|
+
const f = Bun.file(join14(worktreePath, p));
|
|
20239
20998
|
const body = f.size > 2000000 ? String(f.size) : Bun.hash(await f.arrayBuffer()).toString(36);
|
|
20240
20999
|
return `${p}:${body}`;
|
|
20241
21000
|
} catch {
|
|
@@ -20268,6 +21027,10 @@ async function fileBefore(worktreePath, defaultBr, file) {
|
|
|
20268
21027
|
const r = await gitRaw(worktreePath, "show", `${ref}:${file}`);
|
|
20269
21028
|
return r.ok ? r.out : "";
|
|
20270
21029
|
}
|
|
21030
|
+
async function fileAt(worktreePath, commit, file) {
|
|
21031
|
+
const r = await gitRaw(worktreePath, "show", `${commit}:${file}`);
|
|
21032
|
+
return r.ok ? r.out : "";
|
|
21033
|
+
}
|
|
20271
21034
|
function parseNumstat(out) {
|
|
20272
21035
|
const counts = new Map;
|
|
20273
21036
|
for (const line of out.split(`
|
|
@@ -20286,7 +21049,7 @@ async function numstat(worktreePath, ...range) {
|
|
|
20286
21049
|
}
|
|
20287
21050
|
async function untrackedLines(worktreePath, file) {
|
|
20288
21051
|
try {
|
|
20289
|
-
const f = Bun.file(
|
|
21052
|
+
const f = Bun.file(join14(worktreePath, file));
|
|
20290
21053
|
if (f.size > 2000000)
|
|
20291
21054
|
return {};
|
|
20292
21055
|
const buf = new Uint8Array(await f.arrayBuffer());
|
|
@@ -20459,7 +21222,7 @@ var COMMON = 40;
|
|
|
20459
21222
|
var NEAR = 3;
|
|
20460
21223
|
var CLEAR_LEAD = 1.5;
|
|
20461
21224
|
var ELEMENT_SOURCES_MAX = 8;
|
|
20462
|
-
var
|
|
21225
|
+
var NAME2 = /^[\w-]+$/;
|
|
20463
21226
|
var TAG = /^[a-z][a-z0-9-]*$/;
|
|
20464
21227
|
var NAME_CHAR = /[\w-]/;
|
|
20465
21228
|
function needlesOf(el) {
|
|
@@ -20472,12 +21235,12 @@ function needlesOf(el) {
|
|
|
20472
21235
|
out.set(text, { text, weight, word });
|
|
20473
21236
|
};
|
|
20474
21237
|
if (el.id)
|
|
20475
|
-
add(el.id, 8,
|
|
21238
|
+
add(el.id, 8, NAME2.test(el.id));
|
|
20476
21239
|
const words = (el.text.split(/[&<>"'`${}]/)[0] ?? "").trim().split(/\s+/).slice(0, 6).join(" ");
|
|
20477
21240
|
if (words.length >= 3 && /\p{L}/u.test(words))
|
|
20478
21241
|
add(words, 6, false);
|
|
20479
21242
|
for (const [, value] of el.attrs)
|
|
20480
|
-
add(value, 5,
|
|
21243
|
+
add(value, 5, NAME2.test(value));
|
|
20481
21244
|
for (const c of el.classes)
|
|
20482
21245
|
add(c, 3, true);
|
|
20483
21246
|
return [...out.values()];
|
|
@@ -20643,7 +21406,7 @@ var NOT_WHERE_WRITTEN = [
|
|
|
20643
21406
|
];
|
|
20644
21407
|
var NO_TEXT = { before: "", after: "" };
|
|
20645
21408
|
async function statFile(target) {
|
|
20646
|
-
const st = await
|
|
21409
|
+
const st = await stat2(target).catch((e) => {
|
|
20647
21410
|
if (e.code === "ENOENT")
|
|
20648
21411
|
return null;
|
|
20649
21412
|
throw e;
|
|
@@ -20704,14 +21467,14 @@ class FileService {
|
|
|
20704
21467
|
throw new UserError("unknown worktree");
|
|
20705
21468
|
return r;
|
|
20706
21469
|
}
|
|
20707
|
-
async read(worktreeId, path, ref) {
|
|
21470
|
+
async read(worktreeId, path, ref, since) {
|
|
20708
21471
|
const r = this.require(worktreeId);
|
|
20709
21472
|
const target = resolveInside(r.path, path);
|
|
20710
21473
|
if (ref) {
|
|
20711
21474
|
const { before, after } = await fileAtCommit(r.path, ref, path);
|
|
20712
21475
|
return { ...sides(before, after), version: null, writable: false };
|
|
20713
21476
|
}
|
|
20714
|
-
const before = await fileBefore(r.path, r.base, path);
|
|
21477
|
+
const before = since ? await fileAt(r.path, since, path) : await fileBefore(r.path, r.base, path);
|
|
20715
21478
|
const st = await statFile(target);
|
|
20716
21479
|
if (st && st.size > FILE_MAX_CHARS) {
|
|
20717
21480
|
return { ...NO_TEXT, version: `${st.size}-${st.mtimeMs}`, writable: false, binary: false, tooLarge: true };
|
|
@@ -20842,7 +21605,7 @@ class FileService {
|
|
|
20842
21605
|
revealPath(target) {
|
|
20843
21606
|
if (process.platform !== "darwin")
|
|
20844
21607
|
throw new UserError("reveal is only available on macOS");
|
|
20845
|
-
const child =
|
|
21608
|
+
const child = spawn5("open", ["-R", target], { stdio: "ignore" });
|
|
20846
21609
|
child.on("error", () => {});
|
|
20847
21610
|
child.unref();
|
|
20848
21611
|
}
|
|
@@ -20890,7 +21653,7 @@ class OpenService {
|
|
|
20890
21653
|
}
|
|
20891
21654
|
async locate(raw) {
|
|
20892
21655
|
const path = canonical(resolve7(expandTilde(raw)));
|
|
20893
|
-
const st = await
|
|
21656
|
+
const st = await stat3(path).catch((e) => {
|
|
20894
21657
|
if (e.code === "ENOENT")
|
|
20895
21658
|
return null;
|
|
20896
21659
|
throw e;
|
|
@@ -21169,9 +21932,9 @@ class AfterLand {
|
|
|
21169
21932
|
}
|
|
21170
21933
|
|
|
21171
21934
|
// ../daemon/src/repos/registry.ts
|
|
21172
|
-
import { existsSync as existsSync20, mkdirSync as
|
|
21935
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync7, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
21173
21936
|
import { homedir as homedir9 } from "os";
|
|
21174
|
-
import { basename as basename5, dirname as dirname7, join as
|
|
21937
|
+
import { basename as basename5, dirname as dirname7, join as join18 } from "path";
|
|
21175
21938
|
|
|
21176
21939
|
// ../daemon/src/git/base.ts
|
|
21177
21940
|
async function resolveBase(repoPath, defaultBranch, route) {
|
|
@@ -21182,8 +21945,8 @@ async function resolveBase(repoPath, defaultBranch, route) {
|
|
|
21182
21945
|
}
|
|
21183
21946
|
|
|
21184
21947
|
// ../daemon/src/repos/config.ts
|
|
21185
|
-
import { closeSync, existsSync as existsSync17, openSync as openSync2, readdirSync as
|
|
21186
|
-
import { join as
|
|
21948
|
+
import { closeSync, existsSync as existsSync17, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync12, readSync, statSync as statSync6 } from "fs";
|
|
21949
|
+
import { join as join15 } from "path";
|
|
21187
21950
|
|
|
21188
21951
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
21189
21952
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -22547,7 +23310,7 @@ function applyEdits(text, edits) {
|
|
|
22547
23310
|
|
|
22548
23311
|
// ../daemon/src/repos/config.ts
|
|
22549
23312
|
var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
|
|
22550
|
-
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync17(
|
|
23313
|
+
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync17(join15(repoPath, rel)));
|
|
22551
23314
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
22552
23315
|
function parseSettings(text) {
|
|
22553
23316
|
const errors = [];
|
|
@@ -22572,7 +23335,7 @@ function readConfigFile(repoPath) {
|
|
|
22572
23335
|
for (const rel of files) {
|
|
22573
23336
|
let raw;
|
|
22574
23337
|
try {
|
|
22575
|
-
raw = parseSettings(readFileSync12(
|
|
23338
|
+
raw = parseSettings(readFileSync12(join15(repoPath, rel), "utf8"));
|
|
22576
23339
|
} catch (e) {
|
|
22577
23340
|
return { ok: false, reason: `${rel} is not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
22578
23341
|
}
|
|
@@ -22631,7 +23394,7 @@ function configTarget(repoPath, made) {
|
|
|
22631
23394
|
}
|
|
22632
23395
|
function configBody(repoPath, rel, config) {
|
|
22633
23396
|
const place = PLACES.find((p) => p.local === rel);
|
|
22634
|
-
const abs = place &&
|
|
23397
|
+
const abs = place && join15(repoPath, place.shared);
|
|
22635
23398
|
if (!abs || !existsSync17(abs))
|
|
22636
23399
|
return config;
|
|
22637
23400
|
let shared;
|
|
@@ -22649,7 +23412,7 @@ function configText(repoPath, rel, config) {
|
|
|
22649
23412
|
let text;
|
|
22650
23413
|
let was;
|
|
22651
23414
|
try {
|
|
22652
|
-
text = readFileSync12(
|
|
23415
|
+
text = readFileSync12(join15(repoPath, rel), "utf8");
|
|
22653
23416
|
was = parseSettings(text);
|
|
22654
23417
|
} catch {
|
|
22655
23418
|
return fresh;
|
|
@@ -22679,7 +23442,7 @@ function detectConfig(repoPath) {
|
|
|
22679
23442
|
return { config: explicit.config, needsSetup: false };
|
|
22680
23443
|
if (explicit)
|
|
22681
23444
|
log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
|
|
22682
|
-
const pkgPath =
|
|
23445
|
+
const pkgPath = join15(repoPath, "package.json");
|
|
22683
23446
|
if (existsSync17(pkgPath)) {
|
|
22684
23447
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
22685
23448
|
const scripts = pkg.scripts ?? {};
|
|
@@ -22702,7 +23465,7 @@ function detectConfig(repoPath) {
|
|
|
22702
23465
|
};
|
|
22703
23466
|
}
|
|
22704
23467
|
}
|
|
22705
|
-
if (existsSync17(
|
|
23468
|
+
if (existsSync17(join15(repoPath, "start.sh"))) {
|
|
22706
23469
|
return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
|
|
22707
23470
|
}
|
|
22708
23471
|
const assumed = assumeNothingToRun(repoPath);
|
|
@@ -22745,24 +23508,24 @@ function assumeNothingToRun(repoPath) {
|
|
|
22745
23508
|
return marker && !servesPage(repoPath) ? marker : undefined;
|
|
22746
23509
|
}
|
|
22747
23510
|
function markerIn(repoPath) {
|
|
22748
|
-
const named = NO_PAGE_MARKERS.find((f) => existsSync17(
|
|
23511
|
+
const named = NO_PAGE_MARKERS.find((f) => existsSync17(join15(repoPath, f)));
|
|
22749
23512
|
if (named)
|
|
22750
23513
|
return named;
|
|
22751
23514
|
let entries;
|
|
22752
23515
|
try {
|
|
22753
|
-
entries =
|
|
23516
|
+
entries = readdirSync5(repoPath);
|
|
22754
23517
|
} catch {
|
|
22755
23518
|
return;
|
|
22756
23519
|
}
|
|
22757
23520
|
return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
22758
23521
|
}
|
|
22759
23522
|
function servesPage(repoPath) {
|
|
22760
|
-
if (WEB_FILES.some((f) => existsSync17(
|
|
23523
|
+
if (WEB_FILES.some((f) => existsSync17(join15(repoPath, f))))
|
|
22761
23524
|
return true;
|
|
22762
|
-
if (/^web:/m.test(head(
|
|
23525
|
+
if (/^web:/m.test(head(join15(repoPath, "Procfile"))))
|
|
22763
23526
|
return true;
|
|
22764
23527
|
for (const [files, names] of WEB_DEPENDENCIES) {
|
|
22765
|
-
const text = files.map((f) => head(
|
|
23528
|
+
const text = files.map((f) => head(join15(repoPath, f))).join(`
|
|
22766
23529
|
`);
|
|
22767
23530
|
if (text.trim() && names.some((n) => mentions(text, n)))
|
|
22768
23531
|
return true;
|
|
@@ -22772,14 +23535,14 @@ function servesPage(repoPath) {
|
|
|
22772
23535
|
function frontEndBeside(repoPath) {
|
|
22773
23536
|
let dirs;
|
|
22774
23537
|
try {
|
|
22775
|
-
dirs =
|
|
23538
|
+
dirs = readdirSync5(repoPath, { withFileTypes: true });
|
|
22776
23539
|
} catch {
|
|
22777
23540
|
return false;
|
|
22778
23541
|
}
|
|
22779
23542
|
return dirs.some((d) => {
|
|
22780
23543
|
if (!d.isDirectory() || d.name.startsWith(".") || NOT_A_PACKAGE.has(d.name))
|
|
22781
23544
|
return false;
|
|
22782
|
-
const pkg = head(
|
|
23545
|
+
const pkg = head(join15(repoPath, d.name, "package.json"));
|
|
22783
23546
|
if (!pkg)
|
|
22784
23547
|
return false;
|
|
22785
23548
|
try {
|
|
@@ -22835,11 +23598,11 @@ function isLocalDatabase(url) {
|
|
|
22835
23598
|
}
|
|
22836
23599
|
function detectServices(repoPath) {
|
|
22837
23600
|
const out = {};
|
|
22838
|
-
const compose = COMPOSE_FILES.find((f) => existsSync17(
|
|
23601
|
+
const compose = COMPOSE_FILES.find((f) => existsSync17(join15(repoPath, f)));
|
|
22839
23602
|
if (compose)
|
|
22840
23603
|
out.compose = compose;
|
|
22841
23604
|
for (const file of ENV_FILES) {
|
|
22842
|
-
const text = head(
|
|
23605
|
+
const text = head(join15(repoPath, file));
|
|
22843
23606
|
if (!text)
|
|
22844
23607
|
continue;
|
|
22845
23608
|
for (const name of DB_URL_NAMES) {
|
|
@@ -22863,7 +23626,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22863
23626
|
return;
|
|
22864
23627
|
let dir = false;
|
|
22865
23628
|
try {
|
|
22866
|
-
dir = statSync6(
|
|
23629
|
+
dir = statSync6(join15(repoPath, clean)).isDirectory();
|
|
22867
23630
|
} catch {
|
|
22868
23631
|
return;
|
|
22869
23632
|
}
|
|
@@ -22874,25 +23637,25 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22874
23637
|
const t = tokens[i];
|
|
22875
23638
|
if (t === "cd") {
|
|
22876
23639
|
const dir = tokens[i + 1];
|
|
22877
|
-
if (dir && !dir.startsWith("-") && existsSync17(
|
|
23640
|
+
if (dir && !dir.startsWith("-") && existsSync17(join15(repoPath, dir)))
|
|
22878
23641
|
add(dir);
|
|
22879
23642
|
continue;
|
|
22880
23643
|
}
|
|
22881
23644
|
if (t.startsWith("-") || t.startsWith("$"))
|
|
22882
23645
|
continue;
|
|
22883
23646
|
const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
|
|
22884
|
-
if (mod?.[1] && existsSync17(
|
|
23647
|
+
if (mod?.[1] && existsSync17(join15(repoPath, mod[1]))) {
|
|
22885
23648
|
add(mod[1]);
|
|
22886
23649
|
continue;
|
|
22887
23650
|
}
|
|
22888
23651
|
const rel = t.replace(/^\.\//, "");
|
|
22889
23652
|
if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
|
|
22890
23653
|
continue;
|
|
22891
|
-
if (!existsSync17(
|
|
23654
|
+
if (!existsSync17(join15(repoPath, rel)))
|
|
22892
23655
|
continue;
|
|
22893
23656
|
add(rel);
|
|
22894
23657
|
if (depth > 0 && rel.endsWith(".sh")) {
|
|
22895
|
-
for (const p of inferPaths(head(
|
|
23658
|
+
for (const p of inferPaths(head(join15(repoPath, rel)), repoPath, depth - 1))
|
|
22896
23659
|
out.add(p);
|
|
22897
23660
|
}
|
|
22898
23661
|
}
|
|
@@ -22915,9 +23678,9 @@ function procCommand(runner, script, body) {
|
|
|
22915
23678
|
return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
|
|
22916
23679
|
}
|
|
22917
23680
|
function detectPages(repoPath, scripts, runner) {
|
|
22918
|
-
if (!scripts.build || !existsSync17(
|
|
23681
|
+
if (!scripts.build || !existsSync17(join15(repoPath, "functions")))
|
|
22919
23682
|
return null;
|
|
22920
|
-
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) =>
|
|
23683
|
+
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join15(repoPath, f)).find((f) => existsSync17(f));
|
|
22921
23684
|
if (!cfg)
|
|
22922
23685
|
return null;
|
|
22923
23686
|
const out = readFileSync12(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
|
|
@@ -22926,20 +23689,20 @@ function detectPages(repoPath, scripts, runner) {
|
|
|
22926
23689
|
return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
|
|
22927
23690
|
}
|
|
22928
23691
|
function detectRunner(repoPath, packageManager) {
|
|
22929
|
-
if (existsSync17(
|
|
23692
|
+
if (existsSync17(join15(repoPath, "bun.lock")) || existsSync17(join15(repoPath, "bun.lockb")))
|
|
22930
23693
|
return "bun";
|
|
22931
|
-
if (existsSync17(
|
|
23694
|
+
if (existsSync17(join15(repoPath, "pnpm-lock.yaml")))
|
|
22932
23695
|
return "pnpm";
|
|
22933
|
-
if (existsSync17(
|
|
23696
|
+
if (existsSync17(join15(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
|
|
22934
23697
|
return "yarn";
|
|
22935
23698
|
return "npm";
|
|
22936
23699
|
}
|
|
22937
23700
|
|
|
22938
23701
|
// ../daemon/src/repos/create.ts
|
|
22939
|
-
import { existsSync as existsSync18, readdirSync as
|
|
23702
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
22940
23703
|
import { appendFile as appendFile2, mkdir as mkdir2, rm } from "fs/promises";
|
|
22941
23704
|
import { homedir as homedir8 } from "os";
|
|
22942
|
-
import { dirname as dirname6, join as
|
|
23705
|
+
import { dirname as dirname6, join as join16, resolve as resolve8 } from "path";
|
|
22943
23706
|
function isInside(dir, parent) {
|
|
22944
23707
|
const p = parent.replace(/\/+$/, "");
|
|
22945
23708
|
return dir === p || dir.startsWith(`${p}/`);
|
|
@@ -22950,29 +23713,29 @@ function planProject(opts, home = homedir8()) {
|
|
|
22950
23713
|
throw new UserError(nameError);
|
|
22951
23714
|
const first = firstProjectsFolder(opts.parent, home);
|
|
22952
23715
|
const parent = first ?? resolveParent(opts.parent);
|
|
22953
|
-
const dir =
|
|
23716
|
+
const dir = join16(parent, opts.name.trim());
|
|
22954
23717
|
if (existsSync18(dir))
|
|
22955
23718
|
throw new UserError(`${dir} already exists`);
|
|
22956
23719
|
return { parent, dir, makeParent: first !== null };
|
|
22957
23720
|
}
|
|
22958
23721
|
function firstProjectsFolder(raw, home) {
|
|
22959
23722
|
const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
|
|
22960
|
-
if (typed !==
|
|
23723
|
+
if (typed !== join16(home, PROJECTS_FOLDER) || existsSync18(typed) || !existsSync18(home))
|
|
22961
23724
|
return null;
|
|
22962
|
-
return
|
|
23725
|
+
return join16(realpathSync2(home), PROJECTS_FOLDER);
|
|
22963
23726
|
}
|
|
22964
23727
|
function planInPlace(opts) {
|
|
22965
23728
|
const name = opts.name.trim();
|
|
22966
23729
|
if (!name || name === "." || name === ".." || name.includes("/")) {
|
|
22967
23730
|
throw new UserError(`${opts.name} is not a folder name`);
|
|
22968
23731
|
}
|
|
22969
|
-
const typed =
|
|
23732
|
+
const typed = join16(resolveParent(opts.parent), name);
|
|
22970
23733
|
if (!existsSync18(typed) || !statSync7(typed).isDirectory())
|
|
22971
23734
|
throw new UserError(`${typed} is not a folder`);
|
|
22972
23735
|
const dir = realpathSync2(typed);
|
|
22973
|
-
if (existsSync18(
|
|
23736
|
+
if (existsSync18(join16(dir, ".git")))
|
|
22974
23737
|
throw new UserError(`${dir} is already a project; open it instead`);
|
|
22975
|
-
if (!holdsNothing(
|
|
23738
|
+
if (!holdsNothing(readdirSync6(dir))) {
|
|
22976
23739
|
throw new UserError(`${dir} is not empty; only an empty folder can become a project where it is`);
|
|
22977
23740
|
}
|
|
22978
23741
|
return { parent: dirname6(dir), dir, makeParent: false };
|
|
@@ -23061,13 +23824,13 @@ async function undoCreate(dir) {
|
|
|
23061
23824
|
}
|
|
23062
23825
|
async function undoInit(dir) {
|
|
23063
23826
|
try {
|
|
23064
|
-
await rm(
|
|
23827
|
+
await rm(join16(dir, ".git"), { recursive: true, force: true });
|
|
23065
23828
|
} catch (e) {
|
|
23066
23829
|
log.warn(dir, "could not take back a half-made .git", e);
|
|
23067
23830
|
}
|
|
23068
23831
|
}
|
|
23069
23832
|
async function unmakeProject(dir, made) {
|
|
23070
|
-
const target = made === "folder" ? dir :
|
|
23833
|
+
const target = made === "folder" ? dir : join16(dir, ".git");
|
|
23071
23834
|
try {
|
|
23072
23835
|
await rm(target, { recursive: true, force: true });
|
|
23073
23836
|
} catch (e) {
|
|
@@ -23098,7 +23861,7 @@ async function ok(p, what) {
|
|
|
23098
23861
|
|
|
23099
23862
|
// ../daemon/src/repos/watcher.ts
|
|
23100
23863
|
import { existsSync as existsSync19, watch } from "fs";
|
|
23101
|
-
import { basename as basename4, join as
|
|
23864
|
+
import { basename as basename4, join as join17 } from "path";
|
|
23102
23865
|
function watchDefaultBranch(repoPath, branch, onMove) {
|
|
23103
23866
|
const last = { local: null, upstream: null };
|
|
23104
23867
|
const read = async () => {
|
|
@@ -23151,13 +23914,13 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
23151
23914
|
]);
|
|
23152
23915
|
if (stopped)
|
|
23153
23916
|
return;
|
|
23154
|
-
const gitDir = common.ok && common.out ? common.out :
|
|
23917
|
+
const gitDir = common.ok && common.out ? common.out : join17(repoPath, ".git");
|
|
23155
23918
|
tryWatch(gitDir);
|
|
23156
|
-
tryWatch(
|
|
23919
|
+
tryWatch(join17(gitDir, "refs", "heads"));
|
|
23157
23920
|
if (!remote.ok || !remote.out)
|
|
23158
23921
|
return;
|
|
23159
|
-
const remotes =
|
|
23160
|
-
const mine =
|
|
23922
|
+
const remotes = join17(gitDir, "refs", "remotes");
|
|
23923
|
+
const mine = join17(remotes, remote.out);
|
|
23161
23924
|
let inner = null;
|
|
23162
23925
|
let mid = null;
|
|
23163
23926
|
const armInner = () => {
|
|
@@ -23173,7 +23936,7 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
23173
23936
|
}) : null;
|
|
23174
23937
|
armInner();
|
|
23175
23938
|
};
|
|
23176
|
-
tryWatch(
|
|
23939
|
+
tryWatch(join17(gitDir, "refs"), (_event, filename) => {
|
|
23177
23940
|
if (!filename || filename === "remotes")
|
|
23178
23941
|
armMid();
|
|
23179
23942
|
schedule();
|
|
@@ -23202,7 +23965,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
23202
23965
|
}, 300);
|
|
23203
23966
|
};
|
|
23204
23967
|
const arm = (commonDir) => {
|
|
23205
|
-
const dir =
|
|
23968
|
+
const dir = join17(commonDir, "worktrees");
|
|
23206
23969
|
const armInner = () => {
|
|
23207
23970
|
inner?.close();
|
|
23208
23971
|
inner = null;
|
|
@@ -23245,7 +24008,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
23245
24008
|
function watchConfigFile(repoPath, onChange) {
|
|
23246
24009
|
const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
|
|
23247
24010
|
const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename4(rel)));
|
|
23248
|
-
const dir =
|
|
24011
|
+
const dir = join17(repoPath, CONFIG_DIR);
|
|
23249
24012
|
let timer = null;
|
|
23250
24013
|
let outer = null;
|
|
23251
24014
|
let inner = null;
|
|
@@ -23382,7 +24145,7 @@ class RepoRegistry {
|
|
|
23382
24145
|
return `${repo.name} is set up now`;
|
|
23383
24146
|
if (main.promptedAt || main.lastTurn)
|
|
23384
24147
|
return `${repo.name} has a chat now`;
|
|
23385
|
-
if (existsSync20(
|
|
24148
|
+
if (existsSync20(join18(repo.path, ".claude")))
|
|
23386
24149
|
return `an agent has already run in ${repo.name}`;
|
|
23387
24150
|
if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
|
|
23388
24151
|
return `${repo.name} has files in it now`;
|
|
@@ -23533,10 +24296,10 @@ class RepoRegistry {
|
|
|
23533
24296
|
throw new UserError(current.reason);
|
|
23534
24297
|
const was = configTarget(repo.path, !!repo.made);
|
|
23535
24298
|
const rel = configSibling(was, kind);
|
|
23536
|
-
const moved = rel !== was && existsSync20(
|
|
24299
|
+
const moved = rel !== was && existsSync20(join18(repo.path, was));
|
|
23537
24300
|
if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
|
|
23538
24301
|
try {
|
|
23539
|
-
|
|
24302
|
+
rmSync3(join18(repo.path, was));
|
|
23540
24303
|
} catch (e) {
|
|
23541
24304
|
log.warn(repoId, `could not remove ${was}`, e);
|
|
23542
24305
|
}
|
|
@@ -23545,9 +24308,9 @@ class RepoRegistry {
|
|
|
23545
24308
|
}
|
|
23546
24309
|
if (isLocalConfigFile(rel))
|
|
23547
24310
|
await excludeFromGit(repo.path, rel);
|
|
23548
|
-
const file =
|
|
24311
|
+
const file = join18(repo.path, rel);
|
|
23549
24312
|
try {
|
|
23550
|
-
|
|
24313
|
+
mkdirSync7(dirname7(file), { recursive: true });
|
|
23551
24314
|
writeFileSync7(file, configText(repo.path, rel, config));
|
|
23552
24315
|
} catch (e) {
|
|
23553
24316
|
log.warn(repoId, `could not write ${rel}`, e);
|
|
@@ -23649,7 +24412,7 @@ class RepoRegistry {
|
|
|
23649
24412
|
}
|
|
23650
24413
|
placeConfig(repo) {
|
|
23651
24414
|
const rel = configTarget(repo.path, !!repo.made);
|
|
23652
|
-
if (isLocalConfigFile(rel) && existsSync20(
|
|
24415
|
+
if (isLocalConfigFile(rel) && existsSync20(join18(repo.path, rel))) {
|
|
23653
24416
|
fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
|
|
23654
24417
|
}
|
|
23655
24418
|
if (repo.configFile === rel)
|
|
@@ -23741,7 +24504,7 @@ class RepoRegistry {
|
|
|
23741
24504
|
}
|
|
23742
24505
|
|
|
23743
24506
|
// ../daemon/src/routes/service.ts
|
|
23744
|
-
import { join as
|
|
24507
|
+
import { join as join19 } from "path";
|
|
23745
24508
|
|
|
23746
24509
|
// ../daemon/src/routes/fileRouters.ts
|
|
23747
24510
|
var SKIP = /(^|\/)(node_modules|dist|build|out|coverage|\.next|\.nuxt|\.output|\.svelte-kit|\.vercel|\.netlify)\//;
|
|
@@ -24941,7 +25704,7 @@ class RouteService {
|
|
|
24941
25704
|
return mergeRoutes(fileRoutes(paths, roots), code);
|
|
24942
25705
|
}
|
|
24943
25706
|
async cachedRead(root, path, had, kept, decode) {
|
|
24944
|
-
const abs =
|
|
25707
|
+
const abs = join19(root, path);
|
|
24945
25708
|
const stat = await this.fs.stat(abs);
|
|
24946
25709
|
if (!stat)
|
|
24947
25710
|
return null;
|
|
@@ -24966,7 +25729,7 @@ class RouteService {
|
|
|
24966
25729
|
}
|
|
24967
25730
|
}
|
|
24968
25731
|
async hash(root, file) {
|
|
24969
|
-
const abs =
|
|
25732
|
+
const abs = join19(root, file);
|
|
24970
25733
|
const stat = await this.fs.stat(abs);
|
|
24971
25734
|
if (!stat)
|
|
24972
25735
|
return null;
|
|
@@ -25288,7 +26051,7 @@ function servedLocally(origin) {
|
|
|
25288
26051
|
import { homedir as homedir11 } from "os";
|
|
25289
26052
|
|
|
25290
26053
|
// ../daemon/src/core/pair.ts
|
|
25291
|
-
import { randomBytes as
|
|
26054
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
25292
26055
|
class PairCodes {
|
|
25293
26056
|
now;
|
|
25294
26057
|
live = null;
|
|
@@ -25296,7 +26059,7 @@ class PairCodes {
|
|
|
25296
26059
|
this.now = now;
|
|
25297
26060
|
}
|
|
25298
26061
|
mint() {
|
|
25299
|
-
const code =
|
|
26062
|
+
const code = randomBytes4(9).toString("base64url");
|
|
25300
26063
|
this.live = { code, until: this.now() + PAIR_TTL_MS };
|
|
25301
26064
|
return { code, ms: PAIR_TTL_MS };
|
|
25302
26065
|
}
|
|
@@ -25307,10 +26070,56 @@ class PairCodes {
|
|
|
25307
26070
|
}
|
|
25308
26071
|
}
|
|
25309
26072
|
|
|
26073
|
+
// ../daemon/src/core/tailnet.ts
|
|
26074
|
+
var PHONE_OS = new Set(["android", "ios"]);
|
|
26075
|
+
function tailnetPhones(json) {
|
|
26076
|
+
let st;
|
|
26077
|
+
try {
|
|
26078
|
+
st = JSON.parse(json);
|
|
26079
|
+
} catch {
|
|
26080
|
+
return null;
|
|
26081
|
+
}
|
|
26082
|
+
if (st === null || typeof st !== "object")
|
|
26083
|
+
return null;
|
|
26084
|
+
const phones = [];
|
|
26085
|
+
for (const p of Object.values(st.Peer ?? {})) {
|
|
26086
|
+
if (!p || !PHONE_OS.has((p.OS ?? "").toLowerCase()))
|
|
26087
|
+
continue;
|
|
26088
|
+
const name = p.HostName || (p.DNSName ?? "").split(".")[0] || "a phone";
|
|
26089
|
+
phones.push({ name, ip: p.TailscaleIPs?.[0] ?? null });
|
|
26090
|
+
}
|
|
26091
|
+
return phones;
|
|
26092
|
+
}
|
|
26093
|
+
var PING_TIMEOUT = "2s";
|
|
26094
|
+
function cli(bin) {
|
|
26095
|
+
return async (args) => {
|
|
26096
|
+
try {
|
|
26097
|
+
const p = Bun.spawn([bin, ...args], { stdin: "ignore", stdout: "pipe", stderr: "ignore", timeout: 5000 });
|
|
26098
|
+
const out = await new Response(p.stdout).text();
|
|
26099
|
+
return { ok: await p.exited === 0, out };
|
|
26100
|
+
} catch {
|
|
26101
|
+
return { ok: false, out: "" };
|
|
26102
|
+
}
|
|
26103
|
+
};
|
|
26104
|
+
}
|
|
26105
|
+
var ping = (ip) => ["ping", "--until-direct=false", "--c", "1", "--timeout", PING_TIMEOUT, ip];
|
|
26106
|
+
async function readTailnetPhones(run = ((bin) => bin ? cli(bin) : null)(Bun.which("tailscale"))) {
|
|
26107
|
+
if (run === null)
|
|
26108
|
+
return null;
|
|
26109
|
+
const st = await run(["status", "--json"]);
|
|
26110
|
+
const listed = st.ok ? tailnetPhones(st.out) : null;
|
|
26111
|
+
if (listed === null)
|
|
26112
|
+
return null;
|
|
26113
|
+
return Promise.all(listed.map(async ({ name, ip }) => ({ name, online: ip !== null && (await run(ping(ip))).ok })));
|
|
26114
|
+
}
|
|
26115
|
+
|
|
26116
|
+
// ../daemon/src/server/handlers.ts
|
|
26117
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
26118
|
+
|
|
25310
26119
|
// ../daemon/src/agent/config.ts
|
|
25311
26120
|
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
|
|
25312
26121
|
import { homedir as homedir10 } from "os";
|
|
25313
|
-
import { join as
|
|
26122
|
+
import { join as join20 } from "path";
|
|
25314
26123
|
function agentConfigFiles(spec, repoPath, home = homedir10()) {
|
|
25315
26124
|
const file = (id, label, path) => ({
|
|
25316
26125
|
id,
|
|
@@ -25320,30 +26129,30 @@ function agentConfigFiles(spec, repoPath, home = homedir10()) {
|
|
|
25320
26129
|
});
|
|
25321
26130
|
if (spec.id === "claude") {
|
|
25322
26131
|
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",
|
|
26132
|
+
file("user-config", "user config (MCP servers, projects)", join20(home, ".claude.json")),
|
|
26133
|
+
file("user-settings", "user settings (permissions, hooks)", join20(home, ".claude", "settings.json")),
|
|
26134
|
+
...repoPath ? [file("project-mcp", "project MCP servers", join20(repoPath, ".mcp.json"))] : [],
|
|
26135
|
+
...repoPath ? [file("project-settings", "project settings", join20(repoPath, ".claude", "settings.json"))] : []
|
|
25327
26136
|
];
|
|
25328
26137
|
}
|
|
25329
26138
|
if (spec.id === "codex")
|
|
25330
|
-
return [file("user-config", "config",
|
|
26139
|
+
return [file("user-config", "config", join20(home, ".codex", "config.toml"))];
|
|
25331
26140
|
return [];
|
|
25332
26141
|
}
|
|
25333
26142
|
function describeAgentConfig(spec, repoPath, home = homedir10()) {
|
|
25334
26143
|
const files = agentConfigFiles(spec, repoPath, home);
|
|
25335
26144
|
const servers = [];
|
|
25336
26145
|
if (spec.id === "claude") {
|
|
25337
|
-
const user = readJson(
|
|
26146
|
+
const user = readJson(join20(home, ".claude.json"));
|
|
25338
26147
|
servers.push(...serversOf(user?.mcpServers, "user"));
|
|
25339
26148
|
if (repoPath) {
|
|
25340
26149
|
const projects = user?.projects;
|
|
25341
26150
|
const mine = projects?.[repoPath];
|
|
25342
26151
|
servers.push(...serversOf(mine?.mcpServers, "local"));
|
|
25343
|
-
servers.push(...serversOf(readJson(
|
|
26152
|
+
servers.push(...serversOf(readJson(join20(repoPath, ".mcp.json"))?.mcpServers, "project"));
|
|
25344
26153
|
}
|
|
25345
26154
|
} else if (spec.id === "codex") {
|
|
25346
|
-
servers.push(...codexServers(
|
|
26155
|
+
servers.push(...codexServers(join20(home, ".codex", "config.toml")));
|
|
25347
26156
|
}
|
|
25348
26157
|
return { agent: spec.id, files, servers };
|
|
25349
26158
|
}
|
|
@@ -25981,6 +26790,9 @@ var handlers = {
|
|
|
25981
26790
|
s.drafts.set(msg.boxId, msg.text, msg.clientId);
|
|
25982
26791
|
s.worktrees.spare.typed(msg.boxId, msg.text);
|
|
25983
26792
|
},
|
|
26793
|
+
"set-attachments"(msg, _ctx, s) {
|
|
26794
|
+
s.drafts.setAttachments(msg.boxId, msg.items, msg.clientId);
|
|
26795
|
+
},
|
|
25984
26796
|
"refresh-git"(msg, _ctx, s) {
|
|
25985
26797
|
s.worktrees.recount(msg.repoId);
|
|
25986
26798
|
},
|
|
@@ -25993,19 +26805,17 @@ var handlers = {
|
|
|
25993
26805
|
"forget-visit"(msg, _ctx, s) {
|
|
25994
26806
|
s.routes.forget(msg.repoId, msg.path);
|
|
25995
26807
|
},
|
|
25996
|
-
async chat(msg,
|
|
25997
|
-
await s.worktrees.send(msg.worktreeId, {
|
|
26808
|
+
async chat(msg, ctx, s) {
|
|
26809
|
+
await sending(s, ctx, msg, msg.attachments, () => s.worktrees.send(msg.worktreeId, {
|
|
25998
26810
|
text: msg.text,
|
|
25999
26811
|
clientId: msg.clientId,
|
|
26000
26812
|
context: msg.context,
|
|
26001
26813
|
attachments: msg.attachments
|
|
26002
|
-
});
|
|
26814
|
+
}));
|
|
26003
26815
|
},
|
|
26004
|
-
async "create-worktree"(msg,
|
|
26005
|
-
|
|
26816
|
+
async "create-worktree"(msg, ctx, s) {
|
|
26817
|
+
const opts = {
|
|
26006
26818
|
createdBy: msg.clientId,
|
|
26007
|
-
worktreeId: msg.worktreeId,
|
|
26008
|
-
variant: msg.variant,
|
|
26009
26819
|
context: msg.context,
|
|
26010
26820
|
attachments: msg.attachments,
|
|
26011
26821
|
agent: msg.agent,
|
|
@@ -26014,6 +26824,30 @@ var handlers = {
|
|
|
26014
26824
|
model: msg.model,
|
|
26015
26825
|
effort: msg.effort,
|
|
26016
26826
|
carry: msg.carry
|
|
26827
|
+
};
|
|
26828
|
+
const of = msg.variants ?? 1;
|
|
26829
|
+
await sending(s, ctx, msg, msg.attachments, async () => {
|
|
26830
|
+
if (of === 1) {
|
|
26831
|
+
await s.worktrees.create(msg.repoId, msg.prompt, { ...opts, worktreeId: msg.worktreeId });
|
|
26832
|
+
return;
|
|
26833
|
+
}
|
|
26834
|
+
const group = randomBytes5(4).toString("hex");
|
|
26835
|
+
const made = await Promise.allSettled(Array.from({ length: of }, (_, i) => s.worktrees.create(msg.repoId, msg.prompt, {
|
|
26836
|
+
...opts,
|
|
26837
|
+
...i === 0 ? { worktreeId: msg.worktreeId } : {},
|
|
26838
|
+
variant: { group, index: i + 1, of }
|
|
26839
|
+
})));
|
|
26840
|
+
const failed = made.flatMap((m) => m.status === "rejected" ? [m.reason] : []);
|
|
26841
|
+
if (failed.length === 0)
|
|
26842
|
+
return;
|
|
26843
|
+
const went = failed.find((e) => e instanceof UserError && e.delivered);
|
|
26844
|
+
if (went)
|
|
26845
|
+
throw went;
|
|
26846
|
+
if (failed.length < of) {
|
|
26847
|
+
const why = failed[0] instanceof Error ? failed[0].message : String(failed[0]);
|
|
26848
|
+
throw new UserError(`${failed.length} of ${of} attempts did not start: ${why}`, { delivered: true });
|
|
26849
|
+
}
|
|
26850
|
+
throw failed[0];
|
|
26017
26851
|
});
|
|
26018
26852
|
},
|
|
26019
26853
|
"set-worktree-profile"(msg, _ctx, s) {
|
|
@@ -26036,6 +26870,8 @@ var handlers = {
|
|
|
26036
26870
|
},
|
|
26037
26871
|
"batch-worktrees"(msg, ctx, s) {
|
|
26038
26872
|
const repo = s.state.requireRepo(msg.repoId);
|
|
26873
|
+
if (msg.boxId)
|
|
26874
|
+
s.drafts.take(msg.boxId);
|
|
26039
26875
|
fireAndForget(msg.repoId, (async () => {
|
|
26040
26876
|
const agent = msg.agent ?? s.state.defaultAgent ?? DEFAULT_AGENT_ID;
|
|
26041
26877
|
const tasks = await s.planTasks(msg.prompt, repo.path, agent) ?? [msg.prompt];
|
|
@@ -26059,23 +26895,34 @@ var handlers = {
|
|
|
26059
26895
|
"list-archived"(msg, ctx, s) {
|
|
26060
26896
|
ctx.reply({ t: "archived", repoId: msg.repoId, items: s.worktrees.archived(msg.repoId) });
|
|
26061
26897
|
},
|
|
26062
|
-
async "restore-worktree"(msg,
|
|
26063
|
-
await s
|
|
26898
|
+
async "restore-worktree"(msg, ctx, s) {
|
|
26899
|
+
await sending(s, ctx, msg, msg.message?.attachments, async () => {
|
|
26900
|
+
await s.worktrees.restore(msg.archiveId, msg.clientId, msg.message);
|
|
26901
|
+
});
|
|
26064
26902
|
},
|
|
26065
26903
|
async "delete-archived"(msg, _ctx, s) {
|
|
26066
26904
|
await s.worktrees.deleteArchived(msg.archiveId);
|
|
26067
26905
|
},
|
|
26068
|
-
async "adopt-worktree"(msg,
|
|
26069
|
-
await s
|
|
26906
|
+
async "adopt-worktree"(msg, ctx, s) {
|
|
26907
|
+
await sending(s, ctx, msg, msg.message?.attachments, async () => {
|
|
26908
|
+
await s.worktrees.adopt(msg.worktreeId, msg.clientId, msg.message);
|
|
26909
|
+
});
|
|
26070
26910
|
},
|
|
26071
26911
|
async "git-status"(msg, ctx, s) {
|
|
26072
26912
|
await gitStatus(s, ctx, msg.worktreeId);
|
|
26073
26913
|
},
|
|
26074
26914
|
async "read-file"(msg, ctx, s) {
|
|
26075
|
-
const head = {
|
|
26915
|
+
const head = {
|
|
26916
|
+
t: "file-read",
|
|
26917
|
+
worktreeId: msg.worktreeId,
|
|
26918
|
+
path: msg.path,
|
|
26919
|
+
ref: msg.ref,
|
|
26920
|
+
since: msg.since,
|
|
26921
|
+
seq: msg.seq
|
|
26922
|
+
};
|
|
26076
26923
|
try {
|
|
26077
26924
|
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);
|
|
26925
|
+
const read = kept ? keptRead(kept.before, kept.after) : await s.files.read(msg.worktreeId, msg.path, msg.ref, msg.since);
|
|
26079
26926
|
ctx.reply({ ...head, ...read });
|
|
26080
26927
|
} catch (e) {
|
|
26081
26928
|
if (!(e instanceof UserError))
|
|
@@ -26105,21 +26952,17 @@ var handlers = {
|
|
|
26105
26952
|
},
|
|
26106
26953
|
async land(msg, ctx, s) {
|
|
26107
26954
|
const { result } = await s.worktrees.land(msg.worktreeId, msg.message);
|
|
26108
|
-
const suggestion = !result.ok && result.conflict ? "Bring main into this branch and resolve the conflicts, then verify the app still works." : undefined;
|
|
26109
26955
|
if (result.ok && (result.pr || s.state.worktree(msg.worktreeId)?.pr)) {
|
|
26110
26956
|
fireAndForget(msg.worktreeId, s.prs.refresh(msg.worktreeId), "pr refresh");
|
|
26111
26957
|
}
|
|
26112
26958
|
await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, {
|
|
26113
26959
|
url: result.url,
|
|
26114
|
-
...
|
|
26960
|
+
...result.asked ? { asked: true } : {}
|
|
26115
26961
|
}));
|
|
26116
26962
|
},
|
|
26117
26963
|
async "sync-main"(msg, ctx, s) {
|
|
26118
|
-
const { result
|
|
26119
|
-
|
|
26120
|
-
await notify(s, ctx, msg.worktreeId, prompt ? shipped(msg.worktreeId, false, `sync conflicts with ${base}: prompt prefilled in chat`, {
|
|
26121
|
-
suggestion: `Merge ${base} into this branch and resolve the conflicts, then verify the app still works.`
|
|
26122
|
-
}) : shipped(msg.worktreeId, result.ok, result.message));
|
|
26964
|
+
const { result } = await s.worktrees.sync(msg.worktreeId);
|
|
26965
|
+
await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, result.asked ? { asked: true } : {}));
|
|
26123
26966
|
},
|
|
26124
26967
|
async "pull-main"(msg, ctx, s) {
|
|
26125
26968
|
const result = await s.worktrees.pull(msg.worktreeId);
|
|
@@ -26148,7 +26991,7 @@ var handlers = {
|
|
|
26148
26991
|
},
|
|
26149
26992
|
async commit(msg, ctx, s) {
|
|
26150
26993
|
const result = await s.worktrees.commit(msg.worktreeId, msg.message);
|
|
26151
|
-
await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message));
|
|
26994
|
+
await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, result.asked ? { asked: true } : {}));
|
|
26152
26995
|
},
|
|
26153
26996
|
async graft(msg, ctx, s) {
|
|
26154
26997
|
const { target } = await s.worktrees.graft(msg.targetId, msg.sourceIds);
|
|
@@ -26188,7 +27031,21 @@ var handlers = {
|
|
|
26188
27031
|
},
|
|
26189
27032
|
unqueue(msg, _ctx, s) {
|
|
26190
27033
|
requireRun(s, msg.worktreeId);
|
|
26191
|
-
s.runtime.agentFor(msg.worktreeId)?.unqueue(msg.index);
|
|
27034
|
+
s.runtime.agentFor(msg.worktreeId)?.unqueue(msg.index, msg.edit ? (m) => s.drafts.putBack(msg.worktreeId, { text: m.text, items: m.attachments ?? [] }) : undefined);
|
|
27035
|
+
},
|
|
27036
|
+
async redraft(msg, _ctx, s) {
|
|
27037
|
+
const r = s.worktrees.readable(msg.worktreeId);
|
|
27038
|
+
const events = r?.wt ? s.runtime.ensureAgent(r.wt).agent.transcript() : s.worktrees.archivedTranscript(msg.worktreeId) ?? [];
|
|
27039
|
+
const event = events.find((e) => e.seq === msg.seq)?.event;
|
|
27040
|
+
if (event?.type !== "user-message")
|
|
27041
|
+
throw new UserError("that message is no longer in this chat");
|
|
27042
|
+
const items = [];
|
|
27043
|
+
for (const ref of event.attachments ?? []) {
|
|
27044
|
+
const item = await s.attachments.reattach(msg.worktreeId, ref, (file) => s.worktrees.archivedAttachment(msg.worktreeId, file));
|
|
27045
|
+
if (item)
|
|
27046
|
+
items.push(item);
|
|
27047
|
+
}
|
|
27048
|
+
s.drafts.putBack(msg.worktreeId, { text: event.text, items });
|
|
26192
27049
|
},
|
|
26193
27050
|
async "changed-ranges"(msg, ctx, s) {
|
|
26194
27051
|
const { ranges, lineOffset } = await s.files.changedRanges(msg.worktreeId, msg.path);
|
|
@@ -26209,6 +27066,12 @@ var handlers = {
|
|
|
26209
27066
|
async judge(msg, _ctx, s) {
|
|
26210
27067
|
await s.landing.judge(msg.worktreeId, msg.note);
|
|
26211
27068
|
},
|
|
27069
|
+
async "fix-preview"(msg, _ctx, s) {
|
|
27070
|
+
await s.worktrees.fixPreview(msg.worktreeId, msg.clientId);
|
|
27071
|
+
},
|
|
27072
|
+
fix(msg, _ctx, s) {
|
|
27073
|
+
s.fix.press(msg.worktreeId, msg.toolId);
|
|
27074
|
+
},
|
|
26212
27075
|
async "write-file"(msg, ctx, s) {
|
|
26213
27076
|
const head = { t: "file-written", worktreeId: msg.worktreeId, path: msg.path, seq: msg.seq };
|
|
26214
27077
|
try {
|
|
@@ -26339,6 +27202,10 @@ var handlers = {
|
|
|
26339
27202
|
s.state.setDefaultAgent(msg.agent);
|
|
26340
27203
|
s.hub.emit("agentsChanged");
|
|
26341
27204
|
},
|
|
27205
|
+
"set-keep-awake"(msg, _ctx, s) {
|
|
27206
|
+
s.state.setKeepAwake(msg.mode);
|
|
27207
|
+
s.hub.emit("keepAwakeChanged");
|
|
27208
|
+
},
|
|
26342
27209
|
"rescan-themes"(_msg, _ctx, s) {
|
|
26343
27210
|
s.themes.load();
|
|
26344
27211
|
s.hub.emit("themesChanged");
|
|
@@ -26388,6 +27255,10 @@ var handlers = {
|
|
|
26388
27255
|
if (!s.runs.stop(msg.worktreeId, msg.kind))
|
|
26389
27256
|
throw new UserError(`no ${msg.kind} is running here`);
|
|
26390
27257
|
},
|
|
27258
|
+
"preview-start"(msg, _ctx, s) {
|
|
27259
|
+
requireRun(s, msg.worktreeId);
|
|
27260
|
+
s.worktrees.startPreview(msg.worktreeId);
|
|
27261
|
+
},
|
|
26391
27262
|
async "search-refs"(msg, ctx, s) {
|
|
26392
27263
|
const refs = await s.refs.search(msg.repoId, msg.query);
|
|
26393
27264
|
ctx.reply({ t: "refs", repoId: msg.repoId, query: msg.query, refs });
|
|
@@ -26400,6 +27271,30 @@ var handlers = {
|
|
|
26400
27271
|
await s.worktrees.openRef(msg.repoId, msg.kind, msg.ref, { createdBy: msg.clientId, pr: msg.pr });
|
|
26401
27272
|
}
|
|
26402
27273
|
};
|
|
27274
|
+
async function sending(s, ctx, from, attachments, deliver) {
|
|
27275
|
+
const uploads = s.attachments.uploads;
|
|
27276
|
+
const { boxId, clientId } = from;
|
|
27277
|
+
const named = uploadIds(attachments);
|
|
27278
|
+
const boxed = boxId ? uploadIds(s.drafts.attachments(boxId)) : [];
|
|
27279
|
+
const ids = [...new Set([...named, ...boxed])].filter((id) => uploads.has(id));
|
|
27280
|
+
uploads.hold(ids);
|
|
27281
|
+
const was = boxId ? s.drafts.take(boxId, clientId) : null;
|
|
27282
|
+
try {
|
|
27283
|
+
if (named.some((id) => !uploads.has(id)))
|
|
27284
|
+
throw new UserError(GONE);
|
|
27285
|
+
await deliver();
|
|
27286
|
+
} catch (e) {
|
|
27287
|
+
if (!boxId || !was || e instanceof UserError && e.delivered)
|
|
27288
|
+
throw e;
|
|
27289
|
+
if (!(e instanceof UserError))
|
|
27290
|
+
log.error("ws", "a message reached no agent", e);
|
|
27291
|
+
const items = was.items.filter((a) => !("upload" in a) || uploads.has(a.upload));
|
|
27292
|
+
s.drafts.putBack(boxId, { text: was.text, items }, clientId);
|
|
27293
|
+
ctx.reply({ t: "unsent", boxId, text: was.text, items, message: e instanceof Error ? e.message : String(e) });
|
|
27294
|
+
} finally {
|
|
27295
|
+
uploads.release(ids);
|
|
27296
|
+
}
|
|
27297
|
+
}
|
|
26403
27298
|
function looseCwd(s, id, stream) {
|
|
26404
27299
|
const disc = s.worktrees.discoveredById(id);
|
|
26405
27300
|
if (!disc)
|
|
@@ -26425,9 +27320,16 @@ async function dispatch(msg, ctx, s) {
|
|
|
26425
27320
|
|
|
26426
27321
|
// ../daemon/src/server/http.ts
|
|
26427
27322
|
import { existsSync as existsSync23 } from "fs";
|
|
26428
|
-
import { join as
|
|
27323
|
+
import { join as join21 } from "path";
|
|
26429
27324
|
var IMMUTABLE = "private, max-age=31536000, immutable";
|
|
26430
27325
|
var NO_STORE = "no-store";
|
|
27326
|
+
function attachedHeaders(imageType) {
|
|
27327
|
+
return {
|
|
27328
|
+
"content-type": imageType ?? "text/plain; charset=utf-8",
|
|
27329
|
+
"x-content-type-options": "nosniff",
|
|
27330
|
+
...imageType ? {} : { "content-security-policy": "sandbox" }
|
|
27331
|
+
};
|
|
27332
|
+
}
|
|
26431
27333
|
function createFetch(opts) {
|
|
26432
27334
|
const grant = previewGrant(opts.token);
|
|
26433
27335
|
const remote = opts.remote;
|
|
@@ -26479,7 +27381,8 @@ function createFetch(opts) {
|
|
|
26479
27381
|
dropped: new Set,
|
|
26480
27382
|
sent: 0,
|
|
26481
27383
|
bytes: 0,
|
|
26482
|
-
view: null
|
|
27384
|
+
view: null,
|
|
27385
|
+
...remoteShell ? { remote: true } : {}
|
|
26483
27386
|
};
|
|
26484
27387
|
const headers = authed && remote && remoteShell ? { "set-cookie": grantCookie(grant, remote.host) } : undefined;
|
|
26485
27388
|
if (srv.upgrade(req, { data, headers }))
|
|
@@ -26515,6 +27418,13 @@ function createFetch(opts) {
|
|
|
26515
27418
|
const body = { code, url: pairLink(remote.host, code), ms };
|
|
26516
27419
|
return Response.json(body, { headers: { "cache-control": NO_STORE } });
|
|
26517
27420
|
}
|
|
27421
|
+
if (url.pathname === "/pair/phones" && req.method === "GET") {
|
|
27422
|
+
if (!sameSecret(req.headers.get("authorization"), `Bearer ${opts.token}`)) {
|
|
27423
|
+
return new Response("unauthorized", { status: 401 });
|
|
27424
|
+
}
|
|
27425
|
+
const phones = remote && isTailnetName(remote.host) ? await opts.phones() : null;
|
|
27426
|
+
return Response.json(phones, { headers: { "cache-control": NO_STORE } });
|
|
27427
|
+
}
|
|
26518
27428
|
if (url.pathname === "/pair/redeem" && req.method === "POST") {
|
|
26519
27429
|
if (!remote || !remoteShell)
|
|
26520
27430
|
return new Response("not found", { status: 404 });
|
|
@@ -26561,6 +27471,37 @@ function createFetch(opts) {
|
|
|
26561
27471
|
return new Response("open failed; see daemon log", { status: 500 });
|
|
26562
27472
|
}
|
|
26563
27473
|
}
|
|
27474
|
+
if (url.pathname === "/uploads" && req.method === "POST") {
|
|
27475
|
+
if (!sameSecret(req.headers.get("authorization"), `Bearer ${opts.token}`)) {
|
|
27476
|
+
return new Response("unauthorized", { status: 401 });
|
|
27477
|
+
}
|
|
27478
|
+
const kind = url.searchParams.get("kind");
|
|
27479
|
+
if (!isUploadKind(kind))
|
|
27480
|
+
return new Response("missing kind", { status: 400 });
|
|
27481
|
+
const mime = (req.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
27482
|
+
try {
|
|
27483
|
+
const body = req.body;
|
|
27484
|
+
return Response.json(await opts.attachments.uploads.put(kind, mime, body), {
|
|
27485
|
+
headers: { "cache-control": NO_STORE }
|
|
27486
|
+
});
|
|
27487
|
+
} catch (e) {
|
|
27488
|
+
if (e instanceof UserError)
|
|
27489
|
+
return new Response(e.message, { status: 400 });
|
|
27490
|
+
log.error("http", "upload failed", e);
|
|
27491
|
+
return new Response("upload failed; see daemon log", { status: 500 });
|
|
27492
|
+
}
|
|
27493
|
+
}
|
|
27494
|
+
if (url.pathname.startsWith("/uploads/")) {
|
|
27495
|
+
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
27496
|
+
return new Response("unauthorized", { status: 401 });
|
|
27497
|
+
const id = url.pathname.slice("/uploads/".length);
|
|
27498
|
+
const path = opts.attachments.uploads.path(id);
|
|
27499
|
+
if (!path || !existsSync23(path))
|
|
27500
|
+
return new Response("not found", { status: 404 });
|
|
27501
|
+
return new Response(Bun.file(path), {
|
|
27502
|
+
headers: { "cache-control": IMMUTABLE, ...attachedHeaders(opts.attachments.uploads.imageType(id)) }
|
|
27503
|
+
});
|
|
27504
|
+
}
|
|
26564
27505
|
if (url.pathname.startsWith("/attachments/")) {
|
|
26565
27506
|
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
26566
27507
|
return new Response("unauthorized", { status: 401 });
|
|
@@ -26571,7 +27512,9 @@ function createFetch(opts) {
|
|
|
26571
27512
|
const path = places.find((p) => !!p && existsSync23(p));
|
|
26572
27513
|
if (!path)
|
|
26573
27514
|
return new Response("not found", { status: 404 });
|
|
26574
|
-
return new Response(Bun.file(path), {
|
|
27515
|
+
return new Response(Bun.file(path), {
|
|
27516
|
+
headers: { "cache-control": IMMUTABLE, ...attachedHeaders(drawnType(file ?? "")) }
|
|
27517
|
+
});
|
|
26575
27518
|
}
|
|
26576
27519
|
if (url.pathname.startsWith("/files/")) {
|
|
26577
27520
|
if (!sameSecret(url.searchParams.get("token"), opts.token))
|
|
@@ -26599,14 +27542,21 @@ function createFetch(opts) {
|
|
|
26599
27542
|
});
|
|
26600
27543
|
}
|
|
26601
27544
|
const rel = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
26602
|
-
const file =
|
|
27545
|
+
const file = join21(opts.shellDist, rel.replaceAll("..", ""));
|
|
26603
27546
|
const hashed = rel.startsWith("/assets/");
|
|
26604
27547
|
if (existsSync23(file) && Bun.file(file).size > 0) {
|
|
27548
|
+
if (rel === "/manifest.json") {
|
|
27549
|
+
const { bar, ground } = opts.manifestColors();
|
|
27550
|
+
const manifest = { ...await Bun.file(file).json(), theme_color: bar, background_color: ground };
|
|
27551
|
+
return Response.json(manifest, {
|
|
27552
|
+
headers: { "content-type": "application/manifest+json", "cache-control": NO_STORE }
|
|
27553
|
+
});
|
|
27554
|
+
}
|
|
26605
27555
|
return new Response(Bun.file(file), { headers: { "cache-control": hashed ? IMMUTABLE : NO_STORE } });
|
|
26606
27556
|
}
|
|
26607
27557
|
if (hashed)
|
|
26608
27558
|
return new Response("not found", { status: 404 });
|
|
26609
|
-
const index =
|
|
27559
|
+
const index = join21(opts.shellDist, "index.html");
|
|
26610
27560
|
if (existsSync23(index))
|
|
26611
27561
|
return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE } });
|
|
26612
27562
|
return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
|
|
@@ -26618,17 +27568,37 @@ var DROP_ABOVE_BYTES = 4 * 1024 * 1024;
|
|
|
26618
27568
|
var DROPPED_NOTICE = `\r
|
|
26619
27569
|
[toyon] output dropped: this pane was too far behind\r
|
|
26620
27570
|
`;
|
|
27571
|
+
var SENDS = new Set(["chat", "create-worktree", "restore-worktree", "batch-worktrees"]);
|
|
27572
|
+
function sentFrom(json) {
|
|
27573
|
+
if (!json || typeof json !== "object")
|
|
27574
|
+
return null;
|
|
27575
|
+
const { t, boxId } = json;
|
|
27576
|
+
if (typeof t !== "string" || !SENDS.has(t))
|
|
27577
|
+
return null;
|
|
27578
|
+
return typeof boxId === "string" && boxId.length > 0 && boxId.length <= 200 ? boxId : null;
|
|
27579
|
+
}
|
|
27580
|
+
function unparsedReply(json, reason, drafts) {
|
|
27581
|
+
const message = `invalid message: ${reason}`;
|
|
27582
|
+
const boxId = sentFrom(json);
|
|
27583
|
+
if (boxId === null)
|
|
27584
|
+
return { t: "error", message };
|
|
27585
|
+
return { t: "unsent", boxId, text: drafts.text(boxId), items: drafts.attachments(boxId), message };
|
|
27586
|
+
}
|
|
26621
27587
|
function startServer(opts) {
|
|
26622
27588
|
const { services: s, token, version } = opts;
|
|
26623
27589
|
const sockets = new Set;
|
|
26624
27590
|
const linked = new Set;
|
|
27591
|
+
const counted = () => {
|
|
27592
|
+
s.idleExit.clients(linked.size);
|
|
27593
|
+
s.keepAwake.remoteShells([...linked].filter((ws) => ws.data.remote || ws.data.preview).length);
|
|
27594
|
+
};
|
|
26625
27595
|
const link = (ws) => {
|
|
26626
27596
|
linked.add(ws);
|
|
26627
|
-
|
|
27597
|
+
counted();
|
|
26628
27598
|
};
|
|
26629
27599
|
const unlink = (ws) => {
|
|
26630
27600
|
if (linked.delete(ws))
|
|
26631
|
-
|
|
27601
|
+
counted();
|
|
26632
27602
|
};
|
|
26633
27603
|
const raw = (ws, json) => {
|
|
26634
27604
|
try {
|
|
@@ -26837,12 +27807,14 @@ function startServer(opts) {
|
|
|
26837
27807
|
agentChosen: s.state.defaultAgent !== undefined
|
|
26838
27808
|
});
|
|
26839
27809
|
s.hub.on("agentsChanged", () => broadcast(agentsMsg()));
|
|
27810
|
+
s.hub.on("keepAwakeChanged", () => broadcast({ t: "keep-awake", mode: s.keepAwake.setting() }));
|
|
26840
27811
|
s.hub.on("selfChanged", () => broadcast({ t: "self", self: s.self.get() }));
|
|
26841
27812
|
s.hub.on("failed", (worktreeId, message) => broadcast({ t: "error", message, worktreeId }));
|
|
26842
27813
|
s.hub.on("updateChanged", () => broadcast({ t: "update", update: s.update.get() }));
|
|
26843
27814
|
s.hub.on("visitsChanged", (repoId) => broadcast({ t: "visits", repoId, pages: s.routes.history(repoId) }));
|
|
26844
27815
|
s.hub.on("archiveChanged", (repoId) => broadcast({ t: "archived", repoId, items: s.worktrees.archived(repoId) }));
|
|
26845
27816
|
s.hub.on("draftChanged", (boxId, text, clientId) => broadcast({ t: "draft", boxId, text, ...clientId ? { clientId } : {} }));
|
|
27817
|
+
s.hub.on("attachmentsChanged", (boxId, items, clientId) => broadcast({ t: "attachments", boxId, items, ...clientId ? { clientId } : {} }));
|
|
26846
27818
|
const helloFrame = async () => {
|
|
26847
27819
|
await s.update.refresh();
|
|
26848
27820
|
return {
|
|
@@ -26861,6 +27833,7 @@ function startServer(opts) {
|
|
|
26861
27833
|
agentChosen: s.state.defaultAgent !== undefined,
|
|
26862
27834
|
home: homedir11(),
|
|
26863
27835
|
folderDialog: process.platform === "darwin" && !cloud.enabled,
|
|
27836
|
+
keepAwake: s.keepAwake.setting(),
|
|
26864
27837
|
remote: opts.remote && { host: opts.remote.host, previews: opts.remote.previews },
|
|
26865
27838
|
paired: s.state.paired,
|
|
26866
27839
|
gitIdentity: await s.repos.gitIdentity(),
|
|
@@ -26869,11 +27842,13 @@ function startServer(opts) {
|
|
|
26869
27842
|
self: s.self.get(),
|
|
26870
27843
|
update: s.update.get(),
|
|
26871
27844
|
drafts: s.drafts.all(),
|
|
27845
|
+
attachments: s.drafts.allAttachments(),
|
|
26872
27846
|
managed: managedView(opts.managed)
|
|
26873
27847
|
};
|
|
26874
27848
|
};
|
|
26875
27849
|
let branded = false;
|
|
26876
27850
|
const serverConfig = {
|
|
27851
|
+
maxRequestBodySize: UPLOAD_MAX_BYTES,
|
|
26877
27852
|
hostname: cloud.bindHost,
|
|
26878
27853
|
fetch: createFetch({
|
|
26879
27854
|
token,
|
|
@@ -26891,6 +27866,10 @@ function startServer(opts) {
|
|
|
26891
27866
|
managed: { source: opts.managed.source, hash: opts.managed.hash },
|
|
26892
27867
|
preview: (id) => s.runtime.get(id)?.proxy?.handler ?? null,
|
|
26893
27868
|
bootstrap: helloFrame,
|
|
27869
|
+
manifestColors: () => {
|
|
27870
|
+
const { surface0, surface1 } = s.themes.current().colors;
|
|
27871
|
+
return { bar: surface1, ground: surface0 };
|
|
27872
|
+
},
|
|
26894
27873
|
open: (path) => s.opens.open(path),
|
|
26895
27874
|
restart: (now) => s.restarter.request({ now }),
|
|
26896
27875
|
restartWait: () => ({ waiting: s.restarter.waitingOn(), asking: s.restarter.asking() }),
|
|
@@ -26898,10 +27877,10 @@ function startServer(opts) {
|
|
|
26898
27877
|
onPaired: () => {
|
|
26899
27878
|
s.state.notePaired();
|
|
26900
27879
|
broadcast({ t: "paired" });
|
|
26901
|
-
}
|
|
27880
|
+
},
|
|
27881
|
+
phones: () => readTailnetPhones()
|
|
26902
27882
|
}),
|
|
26903
27883
|
websocket: {
|
|
26904
|
-
maxPayloadLength: 64 * 1024 * 1024,
|
|
26905
27884
|
async open(ws) {
|
|
26906
27885
|
const preview = ws.data.preview;
|
|
26907
27886
|
if (preview) {
|
|
@@ -26943,7 +27922,7 @@ function startServer(opts) {
|
|
|
26943
27922
|
}
|
|
26944
27923
|
const parsed = parseClientMsg(json);
|
|
26945
27924
|
if (!parsed.ok) {
|
|
26946
|
-
send(ws,
|
|
27925
|
+
send(ws, unparsedReply(json, parsed.reason, s.drafts));
|
|
26947
27926
|
return;
|
|
26948
27927
|
}
|
|
26949
27928
|
const ctx = {
|
|
@@ -27010,16 +27989,16 @@ function startServer(opts) {
|
|
|
27010
27989
|
}
|
|
27011
27990
|
|
|
27012
27991
|
// ../daemon/src/themes/store.ts
|
|
27013
|
-
import { existsSync as existsSync24, readdirSync as
|
|
27992
|
+
import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync15, statSync as statSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
27014
27993
|
import { homedir as homedir12 } from "os";
|
|
27015
|
-
import { basename as basename6, dirname as dirname8, join as
|
|
27994
|
+
import { basename as basename6, dirname as dirname8, join as join22, resolve as resolve9 } from "path";
|
|
27016
27995
|
var home = homedir12();
|
|
27017
27996
|
var defaultExtensionDirs = [
|
|
27018
|
-
|
|
27019
|
-
|
|
27020
|
-
|
|
27021
|
-
|
|
27022
|
-
|
|
27997
|
+
join22(home, ".vscode", "extensions"),
|
|
27998
|
+
join22(home, ".vscode-insiders", "extensions"),
|
|
27999
|
+
join22(home, ".vscode-oss", "extensions"),
|
|
28000
|
+
join22(home, ".cursor", "extensions"),
|
|
28001
|
+
join22(home, ".windsurf", "extensions"),
|
|
27023
28002
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/extensions",
|
|
27024
28003
|
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions",
|
|
27025
28004
|
"/Applications/Cursor.app/Contents/Resources/app/extensions",
|
|
@@ -27103,7 +28082,7 @@ class ThemeStore {
|
|
|
27103
28082
|
name: json?.name ?? base,
|
|
27104
28083
|
source: "file"
|
|
27105
28084
|
});
|
|
27106
|
-
writeFileSync8(
|
|
28085
|
+
writeFileSync8(join22(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
|
|
27107
28086
|
`);
|
|
27108
28087
|
this.load();
|
|
27109
28088
|
return theme;
|
|
@@ -27135,10 +28114,10 @@ function loadDir(dir) {
|
|
|
27135
28114
|
if (!existsSync24(dir))
|
|
27136
28115
|
return [];
|
|
27137
28116
|
const out = [];
|
|
27138
|
-
for (const f of
|
|
28117
|
+
for (const f of readdirSync7(dir)) {
|
|
27139
28118
|
if (!/\.jsonc?$/i.test(f))
|
|
27140
28119
|
continue;
|
|
27141
|
-
const file =
|
|
28120
|
+
const file = join22(dir, f);
|
|
27142
28121
|
const id = `file:${slug(f.replace(/\.(json|jsonc)$/i, ""))}`;
|
|
27143
28122
|
try {
|
|
27144
28123
|
const json = readJsonc(file);
|
|
@@ -27159,13 +28138,13 @@ function discover(dir) {
|
|
|
27159
28138
|
const out = [];
|
|
27160
28139
|
let entries;
|
|
27161
28140
|
try {
|
|
27162
|
-
entries =
|
|
28141
|
+
entries = readdirSync7(dir);
|
|
27163
28142
|
} catch {
|
|
27164
28143
|
return out;
|
|
27165
28144
|
}
|
|
27166
28145
|
for (const name of entries) {
|
|
27167
|
-
const extDir =
|
|
27168
|
-
const pkgFile =
|
|
28146
|
+
const extDir = join22(dir, name);
|
|
28147
|
+
const pkgFile = join22(extDir, "package.json");
|
|
27169
28148
|
try {
|
|
27170
28149
|
if (!statSync9(extDir).isDirectory() || !existsSync24(pkgFile))
|
|
27171
28150
|
continue;
|
|
@@ -27175,7 +28154,7 @@ function discover(dir) {
|
|
|
27175
28154
|
continue;
|
|
27176
28155
|
const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
|
|
27177
28156
|
let nls = {};
|
|
27178
|
-
const nlsFile =
|
|
28157
|
+
const nlsFile = join22(extDir, "package.nls.json");
|
|
27179
28158
|
if (existsSync24(nlsFile)) {
|
|
27180
28159
|
try {
|
|
27181
28160
|
nls = JSON.parse(readFileSync15(nlsFile, "utf8"));
|
|
@@ -27591,8 +28570,8 @@ class BackendShare {
|
|
|
27591
28570
|
// ../daemon/src/worktrees/cache.ts
|
|
27592
28571
|
import { createHash as createHash3 } from "crypto";
|
|
27593
28572
|
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
|
|
28573
|
+
import { mkdir as mkdir4, readdir as readdir2, rename, rm as rm3, stat as stat4, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
28574
|
+
import { join as join23 } from "path";
|
|
27596
28575
|
|
|
27597
28576
|
// ../daemon/src/core/clone.ts
|
|
27598
28577
|
import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
@@ -27623,7 +28602,7 @@ var STALE_MS = 30 * 24 * 3600000;
|
|
|
27623
28602
|
var TMP = ".tmp-";
|
|
27624
28603
|
var MANIFEST = "manifest.json";
|
|
27625
28604
|
function defaultTools(dir) {
|
|
27626
|
-
const has = (...files) => files.some((f) => existsSync25(
|
|
28605
|
+
const has = (...files) => files.some((f) => existsSync25(join23(dir, f)));
|
|
27627
28606
|
const out = [];
|
|
27628
28607
|
if (has("bun.lock", "bun.lockb"))
|
|
27629
28608
|
out.push("bun --version");
|
|
@@ -27636,7 +28615,7 @@ function defaultTools(dir) {
|
|
|
27636
28615
|
return out;
|
|
27637
28616
|
}
|
|
27638
28617
|
function withSidecars(root, rel) {
|
|
27639
|
-
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(
|
|
28618
|
+
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(join23(root, n)));
|
|
27640
28619
|
}
|
|
27641
28620
|
var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
|
|
27642
28621
|
async function listEntries(familyDir) {
|
|
@@ -27651,8 +28630,8 @@ async function listEntries(familyDir) {
|
|
|
27651
28630
|
const m = /^([0-9a-f]{12})-([0-9a-z]+)$/.exec(name);
|
|
27652
28631
|
if (!m?.[1] || !m[2])
|
|
27653
28632
|
continue;
|
|
27654
|
-
const dir =
|
|
27655
|
-
if (!existsSync25(
|
|
28633
|
+
const dir = join23(familyDir, name);
|
|
28634
|
+
if (!existsSync25(join23(dir, MANIFEST)))
|
|
27656
28635
|
continue;
|
|
27657
28636
|
out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
|
|
27658
28637
|
}
|
|
@@ -27682,7 +28661,7 @@ class ArtifactCache {
|
|
|
27682
28661
|
try {
|
|
27683
28662
|
const family = await this.family(wt, repo, policy);
|
|
27684
28663
|
const base = await this.baseCommit(wt, repo);
|
|
27685
|
-
const entries = await listEntries(
|
|
28664
|
+
const entries = await listEntries(join23(this.d.paths.cacheDir, repo.id, family.key));
|
|
27686
28665
|
const entry = entries.find((e) => e.base === base.slice(0, 12)) ?? entries[0];
|
|
27687
28666
|
if (!entry) {
|
|
27688
28667
|
say(`cache: nothing kept for these lockfiles and tools on ${family.platform}`);
|
|
@@ -27690,14 +28669,14 @@ class ArtifactCache {
|
|
|
27690
28669
|
}
|
|
27691
28670
|
const restored = [];
|
|
27692
28671
|
for (const rel of policy.paths) {
|
|
27693
|
-
if (existsSync25(
|
|
28672
|
+
if (existsSync25(join23(wt.path, rel)))
|
|
27694
28673
|
continue;
|
|
27695
28674
|
const names = withSidecars(entry.dir, rel);
|
|
27696
28675
|
if (names.length === 0)
|
|
27697
28676
|
continue;
|
|
27698
28677
|
let whole = true;
|
|
27699
28678
|
for (const name of names) {
|
|
27700
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
28679
|
+
const r = await (this.d.clone ?? cloneTree)(join23(entry.dir, name), join23(wt.path, name));
|
|
27701
28680
|
if (r.ok)
|
|
27702
28681
|
continue;
|
|
27703
28682
|
say(`cache: could not restore ${name}: ${r.error}`);
|
|
@@ -27731,12 +28710,12 @@ class ArtifactCache {
|
|
|
27731
28710
|
let tmp;
|
|
27732
28711
|
let slot;
|
|
27733
28712
|
try {
|
|
27734
|
-
const present = policy.paths.filter((rel) => existsSync25(
|
|
28713
|
+
const present = policy.paths.filter((rel) => existsSync25(join23(wt.path, rel)));
|
|
27735
28714
|
if (present.length === 0)
|
|
27736
28715
|
return;
|
|
27737
28716
|
const family = await this.family(wt, repo, policy);
|
|
27738
28717
|
const base = await this.baseCommit(wt, repo);
|
|
27739
|
-
const familyDir =
|
|
28718
|
+
const familyDir = join23(this.d.paths.cacheDir, repo.id, family.key);
|
|
27740
28719
|
const key = `${familyDir}/${base.slice(0, 12)}`;
|
|
27741
28720
|
const kept = (await listEntries(familyDir)).find((e) => e.base === base.slice(0, 12));
|
|
27742
28721
|
if (kept || this.making.has(key)) {
|
|
@@ -27747,21 +28726,21 @@ class ArtifactCache {
|
|
|
27747
28726
|
this.making.add(key);
|
|
27748
28727
|
const at = Date.now();
|
|
27749
28728
|
const name = entryName(base, at);
|
|
27750
|
-
tmp =
|
|
28729
|
+
tmp = join23(familyDir, `${TMP}${name}`);
|
|
27751
28730
|
await mkdir4(tmp, { recursive: true });
|
|
27752
28731
|
let how = "clonefile";
|
|
27753
28732
|
for (const rel of present) {
|
|
27754
28733
|
for (const n of withSidecars(wt.path, rel)) {
|
|
27755
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
28734
|
+
const r = await (this.d.clone ?? cloneTree)(join23(wt.path, n), join23(tmp, n));
|
|
27756
28735
|
if (!r.ok)
|
|
27757
28736
|
throw new Error(`could not keep ${n}: ${r.error}`);
|
|
27758
28737
|
how = r.how;
|
|
27759
28738
|
}
|
|
27760
28739
|
}
|
|
27761
28740
|
const manifest = { at, base, platform: family.platform, tools: family.tools, paths: present };
|
|
27762
|
-
await writeFile2(
|
|
28741
|
+
await writeFile2(join23(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
|
|
27763
28742
|
`);
|
|
27764
|
-
await rename(tmp,
|
|
28743
|
+
await rename(tmp, join23(familyDir, name));
|
|
27765
28744
|
tmp = undefined;
|
|
27766
28745
|
log.info(worktreeId, `cache: kept ${list(present)} for ${base.slice(0, 7)} via ${how} in ${Date.now() - started}ms`);
|
|
27767
28746
|
await this.prune(familyDir);
|
|
@@ -27789,7 +28768,7 @@ class ArtifactCache {
|
|
|
27789
28768
|
return;
|
|
27790
28769
|
}
|
|
27791
28770
|
for (const repoId of repoIds) {
|
|
27792
|
-
const repoDir =
|
|
28771
|
+
const repoDir = join23(root, repoId);
|
|
27793
28772
|
if (!this.d.state.repo(repoId)) {
|
|
27794
28773
|
await rm3(repoDir, { recursive: true, force: true });
|
|
27795
28774
|
continue;
|
|
@@ -27801,7 +28780,7 @@ class ArtifactCache {
|
|
|
27801
28780
|
continue;
|
|
27802
28781
|
}
|
|
27803
28782
|
for (const family of families) {
|
|
27804
|
-
const familyDir =
|
|
28783
|
+
const familyDir = join23(repoDir, family);
|
|
27805
28784
|
let names;
|
|
27806
28785
|
try {
|
|
27807
28786
|
names = await readdir2(familyDir);
|
|
@@ -27810,11 +28789,11 @@ class ArtifactCache {
|
|
|
27810
28789
|
}
|
|
27811
28790
|
for (const n of names)
|
|
27812
28791
|
if (n.startsWith(TMP))
|
|
27813
|
-
await rm3(
|
|
28792
|
+
await rm3(join23(familyDir, n), { recursive: true, force: true });
|
|
27814
28793
|
const entries = await listEntries(familyDir);
|
|
27815
28794
|
let newest = 0;
|
|
27816
28795
|
for (const e of entries) {
|
|
27817
|
-
const s = await
|
|
28796
|
+
const s = await stat4(e.dir).catch(() => null);
|
|
27818
28797
|
if (s)
|
|
27819
28798
|
newest = Math.max(newest, s.mtimeMs);
|
|
27820
28799
|
}
|
|
@@ -27830,7 +28809,7 @@ class ArtifactCache {
|
|
|
27830
28809
|
for (const f of policy.key) {
|
|
27831
28810
|
h.update(`\x00${f}\x00`);
|
|
27832
28811
|
try {
|
|
27833
|
-
h.update(readFileSync16(
|
|
28812
|
+
h.update(readFileSync16(join23(wt.path, f)));
|
|
27834
28813
|
} catch {
|
|
27835
28814
|
h.update("(absent)");
|
|
27836
28815
|
}
|
|
@@ -27995,6 +28974,92 @@ class ChatSearch {
|
|
|
27995
28974
|
}
|
|
27996
28975
|
}
|
|
27997
28976
|
|
|
28977
|
+
// ../daemon/src/worktrees/fix.ts
|
|
28978
|
+
var RESPONSE = {
|
|
28979
|
+
hook: { does: "ask" },
|
|
28980
|
+
conflict: { does: "ask" },
|
|
28981
|
+
check: { does: "ask", once: true },
|
|
28982
|
+
command: { does: "offer" },
|
|
28983
|
+
preview: { does: "offer" }
|
|
28984
|
+
};
|
|
28985
|
+
function lastAsked(entries) {
|
|
28986
|
+
for (let i = entries.length - 1;i >= 0; i--) {
|
|
28987
|
+
const e = entries[i].event;
|
|
28988
|
+
if (e.type === "fix-asked")
|
|
28989
|
+
return e.kind;
|
|
28990
|
+
if (e.type === "user-message")
|
|
28991
|
+
return;
|
|
28992
|
+
}
|
|
28993
|
+
return;
|
|
28994
|
+
}
|
|
28995
|
+
function askedFor(entries, toolId) {
|
|
28996
|
+
return entries.some(({ event }) => event.type === "fix-asked" && event.toolId === toolId);
|
|
28997
|
+
}
|
|
28998
|
+
function failureOf(entries, toolId) {
|
|
28999
|
+
let command;
|
|
29000
|
+
for (const { event } of entries) {
|
|
29001
|
+
if (event.type === "tool-start" && event.toolId === toolId) {
|
|
29002
|
+
const input = event.input;
|
|
29003
|
+
command = typeof input?.command === "string" ? input.command : undefined;
|
|
29004
|
+
} else if (event.type === "tool-end" && event.toolId === toolId) {
|
|
29005
|
+
if (!event.fixable || command === undefined)
|
|
29006
|
+
return null;
|
|
29007
|
+
const run = { toolId, command, text: unformatOutput(event.output ?? "") };
|
|
29008
|
+
if (event.fixable.kind === "hook")
|
|
29009
|
+
return { kind: "hook", hook: event.fixable.hook ?? "git", ...run };
|
|
29010
|
+
return { kind: event.fixable.kind, ...run };
|
|
29011
|
+
}
|
|
29012
|
+
}
|
|
29013
|
+
return null;
|
|
29014
|
+
}
|
|
29015
|
+
|
|
29016
|
+
class FixService {
|
|
29017
|
+
d;
|
|
29018
|
+
constructor(d) {
|
|
29019
|
+
this.d = d;
|
|
29020
|
+
d.hub.on("checkFailed", (id, run) => {
|
|
29021
|
+
this.report(id, { kind: "check", ...run });
|
|
29022
|
+
});
|
|
29023
|
+
}
|
|
29024
|
+
report(worktreeId, failure, opts = {}) {
|
|
29025
|
+
const response = RESPONSE[failure.kind];
|
|
29026
|
+
if (response.does === "offer" && !opts.pressed)
|
|
29027
|
+
return false;
|
|
29028
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
29029
|
+
if (!wt || isMain(wt))
|
|
29030
|
+
return false;
|
|
29031
|
+
const agent = this.d.runtime.agentFor(worktreeId);
|
|
29032
|
+
if (!agent)
|
|
29033
|
+
return false;
|
|
29034
|
+
const entries = agent.transcript();
|
|
29035
|
+
if (response.once && !opts.pressed && lastAsked(entries) === failure.kind)
|
|
29036
|
+
return false;
|
|
29037
|
+
const toolId = failedRun(failure)?.toolId;
|
|
29038
|
+
if (toolId && askedFor(entries, toolId))
|
|
29039
|
+
return false;
|
|
29040
|
+
const context = failureContext(failure);
|
|
29041
|
+
agent.send(fixPrompt(failure), {
|
|
29042
|
+
asked: { kind: failure.kind, why: fixWhy(failure), ...toolId ? { toolId } : {} },
|
|
29043
|
+
...context ? { context: [context] } : {}
|
|
29044
|
+
});
|
|
29045
|
+
return true;
|
|
29046
|
+
}
|
|
29047
|
+
press(worktreeId, toolId) {
|
|
29048
|
+
this.d.state.requireWorktree(worktreeId);
|
|
29049
|
+
const agent = this.d.runtime.agentFor(worktreeId);
|
|
29050
|
+
if (!agent)
|
|
29051
|
+
throw new UserError("worktree still starting; try again in a moment");
|
|
29052
|
+
const entries = agent.transcript();
|
|
29053
|
+
const failure = failureOf(entries, toolId);
|
|
29054
|
+
if (!failure)
|
|
29055
|
+
throw new UserError("nothing to fix on that row");
|
|
29056
|
+
if (askedFor(entries, toolId))
|
|
29057
|
+
return;
|
|
29058
|
+
if (!this.report(worktreeId, failure, { pressed: true }))
|
|
29059
|
+
throw new UserError("nothing runs on main");
|
|
29060
|
+
}
|
|
29061
|
+
}
|
|
29062
|
+
|
|
27998
29063
|
// ../daemon/src/worktrees/landing.ts
|
|
27999
29064
|
var TAIL_CHARS = 400;
|
|
28000
29065
|
var DIFF_CHARS = 2000;
|
|
@@ -28008,6 +29073,7 @@ class LandingService {
|
|
|
28008
29073
|
constructor(d) {
|
|
28009
29074
|
this.d = d;
|
|
28010
29075
|
d.hub.on("turnSettled", (id, turn) => fireAndForget(id, this.settle(id, turn), "landing verdict"));
|
|
29076
|
+
d.hub.on("messageRefused", (id, said) => fireAndForget(id, this.judge(id, `The commit-msg hook refused the last message, saying: ${said}`), "landing message after a refusal"));
|
|
28011
29077
|
d.hub.on("agentStatus", (id, status) => {
|
|
28012
29078
|
this.note(id, status);
|
|
28013
29079
|
if (status === "working")
|
|
@@ -28178,6 +29244,7 @@ class LandingService {
|
|
|
28178
29244
|
let check = "none";
|
|
28179
29245
|
let checkTail;
|
|
28180
29246
|
let waited = 0;
|
|
29247
|
+
let ran;
|
|
28181
29248
|
const command = repo.config.check?.trim();
|
|
28182
29249
|
if (command) {
|
|
28183
29250
|
const ceiling = timeoutFor(repo.config, "check");
|
|
@@ -28201,6 +29268,7 @@ class LandingService {
|
|
|
28201
29268
|
run.finish(r.exit);
|
|
28202
29269
|
if (!live())
|
|
28203
29270
|
return;
|
|
29271
|
+
ran = r;
|
|
28204
29272
|
check = r.exit === 0 ? "pass" : "fail";
|
|
28205
29273
|
if (r.exit === "timeout") {
|
|
28206
29274
|
checkTail = [`gave up after ${describeDuration(ceiling)}`, tail(r.text)].filter(Boolean).join(`
|
|
@@ -28256,6 +29324,8 @@ class LandingService {
|
|
|
28256
29324
|
record.recap = { at: Date.now(), text: verdict.recap };
|
|
28257
29325
|
log.info(worktreeId, `landing: check ${check}${landing.why ? ", doubted" : ""}${opts.ask ? "" : ", check only"}, ${Date.now() - started}ms${waited > 100 ? ` (waited ${waited}ms for a check slot)` : ""}`);
|
|
28258
29326
|
this.d.worktrees.setLanding(worktreeId, landing);
|
|
29327
|
+
if (command && check === "fail" && ran && typeof ran.exit === "number" && !opts.quiet)
|
|
29328
|
+
this.d.hub.emit("checkFailed", worktreeId, { toolId: ran.toolId, command, text: ran.text });
|
|
28259
29329
|
}
|
|
28260
29330
|
async sharedMigration(wt, repo) {
|
|
28261
29331
|
if (!repo.services || keepsCopiesApart(repo.config))
|
|
@@ -28660,12 +29730,12 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
|
|
|
28660
29730
|
}
|
|
28661
29731
|
|
|
28662
29732
|
// ../daemon/src/worktrees/service.ts
|
|
28663
|
-
import { cpSync, existsSync as
|
|
29733
|
+
import { cpSync, existsSync as existsSync30, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
|
|
28664
29734
|
import { rm as rm4 } from "fs/promises";
|
|
28665
|
-
import { dirname as dirname11, join as
|
|
29735
|
+
import { dirname as dirname11, join as join27 } from "path";
|
|
28666
29736
|
|
|
28667
29737
|
// ../daemon/src/git/archive.ts
|
|
28668
|
-
import { rmSync as
|
|
29738
|
+
import { rmSync as rmSync4 } from "fs";
|
|
28669
29739
|
var archiveRef = (worktreeId) => `refs/toyon/archive/${worktreeId}`;
|
|
28670
29740
|
var SNAPSHOT_SUBJECT = "toyon: uncommitted work";
|
|
28671
29741
|
var SNAPSHOT_AUTHOR = "toyon";
|
|
@@ -28708,7 +29778,7 @@ async function snapshotCommit(wtPath, head, indexFile) {
|
|
|
28708
29778
|
], wtPath);
|
|
28709
29779
|
return commit.ok ? commit.out : null;
|
|
28710
29780
|
} finally {
|
|
28711
|
-
|
|
29781
|
+
rmSync4(indexFile, { force: true });
|
|
28712
29782
|
}
|
|
28713
29783
|
}
|
|
28714
29784
|
async function keptAt(repoPath, ref) {
|
|
@@ -28786,7 +29856,11 @@ async function dropLandRefs(repoPath, worktreeId) {
|
|
|
28786
29856
|
}
|
|
28787
29857
|
|
|
28788
29858
|
// ../daemon/src/git/land.ts
|
|
29859
|
+
import { existsSync as existsSync26 } from "fs";
|
|
28789
29860
|
var STEP_TIMEOUT_MS = 10 * 60000;
|
|
29861
|
+
function rowOf(r) {
|
|
29862
|
+
return r.shown && r.toolId && r.command ? { toolId: r.toolId, command: r.command, text: r.text } : undefined;
|
|
29863
|
+
}
|
|
28790
29864
|
var stepRun = (cwd, args, onText, signal, opts = {}) => runWatched(GIT, args, cwd, { env: NO_PROMPT, onText, signal, ...opts, timeoutMs: opts.timeoutMs ?? STEP_TIMEOUT_MS });
|
|
28791
29865
|
var UNWATCHED = {
|
|
28792
29866
|
step() {},
|
|
@@ -28799,6 +29873,14 @@ function refused(what, r, n = 200) {
|
|
|
28799
29873
|
}
|
|
28800
29874
|
return `${what}: ${r.shown ? "what git and its hooks printed is on the chat" : r.err.slice(0, n)}`;
|
|
28801
29875
|
}
|
|
29876
|
+
function stepRefused(what, r, n = 200) {
|
|
29877
|
+
const row = typeof r.exit === "number" && r.hook ? rowOf(r) : undefined;
|
|
29878
|
+
return {
|
|
29879
|
+
ok: false,
|
|
29880
|
+
message: refused(what, r, n),
|
|
29881
|
+
...row && r.hook ? { hook: { ...row, name: r.hook } } : {}
|
|
29882
|
+
};
|
|
29883
|
+
}
|
|
28802
29884
|
async function commitWorktree(worktreePath, message, w = UNWATCHED) {
|
|
28803
29885
|
if ((await statusFiles(worktreePath)).length === 0)
|
|
28804
29886
|
return { ok: false, message: "nothing to commit" };
|
|
@@ -28806,7 +29888,7 @@ async function commitWorktree(worktreePath, message, w = UNWATCHED) {
|
|
|
28806
29888
|
w.step("committing");
|
|
28807
29889
|
const c = await w.git(worktreePath, ["commit", "-m", message]);
|
|
28808
29890
|
if (!c.ok)
|
|
28809
|
-
return
|
|
29891
|
+
return stepRefused("commit refused", c);
|
|
28810
29892
|
return { ok: true, message: `committed: ${message}` };
|
|
28811
29893
|
}
|
|
28812
29894
|
async function requireClean(worktreePath) {
|
|
@@ -28892,7 +29974,27 @@ async function putBack(worktreePath, defaultBr, aside, taken) {
|
|
|
28892
29974
|
message: `the uncommitted changes here conflict with ${defaultBr}${named}: commit them first, then sync`
|
|
28893
29975
|
};
|
|
28894
29976
|
}
|
|
29977
|
+
var MIDWAY = [
|
|
29978
|
+
["MERGE_HEAD", "merge"],
|
|
29979
|
+
["rebase-merge", "rebase"],
|
|
29980
|
+
["rebase-apply", "rebase"],
|
|
29981
|
+
["CHERRY_PICK_HEAD", "cherry-pick"],
|
|
29982
|
+
["REVERT_HEAD", "revert"]
|
|
29983
|
+
];
|
|
29984
|
+
async function midway(worktreePath) {
|
|
29985
|
+
const args = MIDWAY.flatMap(([file]) => ["--git-path", file]);
|
|
29986
|
+
const r = await git(worktreePath, "rev-parse", "--path-format=absolute", ...args);
|
|
29987
|
+
if (!r.ok)
|
|
29988
|
+
return null;
|
|
29989
|
+
const at = r.out.split(`
|
|
29990
|
+
`).findIndex((file) => file && existsSync26(file));
|
|
29991
|
+
return MIDWAY[at]?.[1] ?? null;
|
|
29992
|
+
}
|
|
28895
29993
|
async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = false) {
|
|
29994
|
+
const unfinished = await midway(worktreePath);
|
|
29995
|
+
if (unfinished) {
|
|
29996
|
+
return { ok: false, message: `a ${unfinished} is unfinished here: finish or abort it first` };
|
|
29997
|
+
}
|
|
28896
29998
|
if (!carry) {
|
|
28897
29999
|
const cErr = await requireClean(worktreePath);
|
|
28898
30000
|
if (cErr)
|
|
@@ -28907,8 +30009,19 @@ async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = f
|
|
|
28907
30009
|
const taken = await bringIn(worktreePath, defaultBr, own, behind, w);
|
|
28908
30010
|
return aside ? putBack(worktreePath, defaultBr, aside, taken) : taken;
|
|
28909
30011
|
}
|
|
30012
|
+
async function requireLinear(worktreePath, base) {
|
|
30013
|
+
const merges = await git(worktreePath, "rev-list", "--merges", "--count", `${base}..HEAD`);
|
|
30014
|
+
if (!merges.ok || merges.out === "0")
|
|
30015
|
+
return null;
|
|
30016
|
+
return {
|
|
30017
|
+
ok: false,
|
|
30018
|
+
conflict: "rebase",
|
|
30019
|
+
message: `the branch holds a merge commit, which landing by rebase would put on ${base}: ask the agent to rebase onto ${base} first`
|
|
30020
|
+
};
|
|
30021
|
+
}
|
|
28910
30022
|
async function bringIn(worktreePath, defaultBr, own, behind, w) {
|
|
28911
|
-
|
|
30023
|
+
const merges = own ? await git(worktreePath, "rev-list", "--merges", "--count", `${defaultBr}..HEAD`) : null;
|
|
30024
|
+
if (own && merges?.out === "0") {
|
|
28912
30025
|
w.step(`rebasing onto ${defaultBr}`);
|
|
28913
30026
|
const r = await w.git(worktreePath, ["rebase", defaultBr]);
|
|
28914
30027
|
if (!r.ok) {
|
|
@@ -28916,10 +30029,12 @@ async function bringIn(worktreePath, defaultBr, own, behind, w) {
|
|
|
28916
30029
|
const files = overwritten(r.text);
|
|
28917
30030
|
if (files.length)
|
|
28918
30031
|
return { ok: false, message: untrackedInTheWay(defaultBr, files) };
|
|
28919
|
-
const ask = `rebase onto ${defaultBr} conflicts: ask the agent to
|
|
30032
|
+
const ask = `rebase onto ${defaultBr} conflicts: ask the agent to rebase onto ${defaultBr} and resolve them`;
|
|
30033
|
+
const step = rowOf(r);
|
|
28920
30034
|
return {
|
|
28921
30035
|
ok: false,
|
|
28922
|
-
conflict:
|
|
30036
|
+
conflict: "rebase",
|
|
30037
|
+
...step ? { step } : {},
|
|
28923
30038
|
message: r.shown ? `${ask}; the conflicts are on the chat` : `${ask} (${r.text.slice(0, 200)})`
|
|
28924
30039
|
};
|
|
28925
30040
|
}
|
|
@@ -28966,7 +30081,7 @@ async function landLocally(worktreePath, branch, repoPath, defaultBr, method, me
|
|
|
28966
30081
|
const c = await w.git(repoPath, ["commit", "-m", message]);
|
|
28967
30082
|
if (!c.ok) {
|
|
28968
30083
|
await git(repoPath, "reset", "--hard", "HEAD");
|
|
28969
|
-
return
|
|
30084
|
+
return stepRefused("squash commit refused", c);
|
|
28970
30085
|
}
|
|
28971
30086
|
return { ok: true, message: `squashed ${branch} onto ${defaultBr}` };
|
|
28972
30087
|
}
|
|
@@ -29025,7 +30140,7 @@ async function landingCommit(worktreePath, branch, base, method, message, own, w
|
|
|
29025
30140
|
const c = await w.git(worktreePath, ["commit", "-m", message]);
|
|
29026
30141
|
if (!c.ok) {
|
|
29027
30142
|
await git(worktreePath, "reset", "--soft", head.out);
|
|
29028
|
-
return
|
|
30143
|
+
return stepRefused("squash commit refused", c);
|
|
29029
30144
|
}
|
|
29030
30145
|
const sha = await git(worktreePath, "rev-parse", "HEAD");
|
|
29031
30146
|
return { ok: true, sha: sha.out, message: `squashed ${branch} onto ${base}` };
|
|
@@ -29038,15 +30153,15 @@ async function landingCommit(worktreePath, branch, base, method, message, own, w
|
|
|
29038
30153
|
w.step(`squashing onto ${base}`);
|
|
29039
30154
|
const s = await w.git(worktreePath, ["merge", "--squash", branch]);
|
|
29040
30155
|
if (!s.ok)
|
|
29041
|
-
return
|
|
30156
|
+
return stepRefused("squash refused", s);
|
|
29042
30157
|
const c = await w.git(worktreePath, ["commit", "-m", message]);
|
|
29043
30158
|
if (!c.ok)
|
|
29044
|
-
return
|
|
30159
|
+
return stepRefused("squash commit refused", c);
|
|
29045
30160
|
} else {
|
|
29046
30161
|
w.step(`merging into ${base}`);
|
|
29047
30162
|
const m = await w.git(worktreePath, ["merge", "--no-ff", "-m", `Merge branch '${branch}'`, branch]);
|
|
29048
30163
|
if (!m.ok)
|
|
29049
|
-
return
|
|
30164
|
+
return stepRefused("merge refused", m);
|
|
29050
30165
|
}
|
|
29051
30166
|
const sha = await git(worktreePath, "rev-parse", "HEAD");
|
|
29052
30167
|
return { ok: true, sha: sha.out, message: `${method === "squash" ? "squashed" : "merged"} ${branch} onto ${base}` };
|
|
@@ -29073,15 +30188,17 @@ async function pushLanding(worktreePath, sha, defaultBr, w = UNWATCHED) {
|
|
|
29073
30188
|
if (p.ok)
|
|
29074
30189
|
return { ok: true, message: `pushed ${defaultBr} to ${tracked.remote}` };
|
|
29075
30190
|
const moved = /\[rejected\]|fetch first|non-fast-forward|cannot lock ref/.test(p.err);
|
|
29076
|
-
return moved ? { ok: false, moved: true, message: `${defaultBr} on ${tracked.remote} moved while landing` } :
|
|
30191
|
+
return moved ? { ok: false, moved: true, message: `${defaultBr} on ${tracked.remote} moved while landing` } : stepRefused("push failed", p, 300);
|
|
29077
30192
|
}
|
|
29078
30193
|
async function mergeFailure(cwd, m, conflictMessage) {
|
|
29079
30194
|
const conflict = /CONFLICT|Automatic merge failed/.test(m.text);
|
|
29080
30195
|
if (conflict) {
|
|
29081
30196
|
await git(cwd, "merge", "--abort");
|
|
30197
|
+
const step = rowOf(m);
|
|
29082
30198
|
return {
|
|
29083
30199
|
ok: false,
|
|
29084
|
-
conflict:
|
|
30200
|
+
conflict: "merge",
|
|
30201
|
+
...step ? { step } : {},
|
|
29085
30202
|
message: m.shown ? `${conflictMessage}; the conflicts are on the chat` : `${conflictMessage} (${m.text.slice(0, 200)})`
|
|
29086
30203
|
};
|
|
29087
30204
|
}
|
|
@@ -29112,7 +30229,7 @@ async function pushBranch(worktreePath, branch, w = UNWATCHED) {
|
|
|
29112
30229
|
push = await w.git(worktreePath, args);
|
|
29113
30230
|
}
|
|
29114
30231
|
if (!push.ok)
|
|
29115
|
-
return
|
|
30232
|
+
return stepRefused("push failed", push, 300);
|
|
29116
30233
|
return { ok: true, message: `pushed ${branch}` };
|
|
29117
30234
|
}
|
|
29118
30235
|
async function goneFromOrigin(worktreePath, branch) {
|
|
@@ -29183,18 +30300,142 @@ function compareUrl(remoteUrl, base, branch) {
|
|
|
29183
30300
|
return `https://github.com/${m[1]}/compare/${encodeURIComponent(base)}...${encodeURIComponent(branch)}?expand=1`;
|
|
29184
30301
|
}
|
|
29185
30302
|
|
|
30303
|
+
// ../daemon/src/git/turn.ts
|
|
30304
|
+
import { copyFileSync, existsSync as existsSync27, rmSync as rmSync5 } from "fs";
|
|
30305
|
+
import { join as join24 } from "path";
|
|
30306
|
+
var turnRef = (worktreeId, which) => `refs/toyon/turns/${worktreeId}/${which}`;
|
|
30307
|
+
var SNAPSHOT_SUBJECT2 = "toyon: tree at a send";
|
|
30308
|
+
var queue = new Map;
|
|
30309
|
+
function inTurn(wtPath, job) {
|
|
30310
|
+
const next = (queue.get(wtPath) ?? Promise.resolve()).then(job, job);
|
|
30311
|
+
queue.set(wtPath, next.catch(() => {}));
|
|
30312
|
+
return next;
|
|
30313
|
+
}
|
|
30314
|
+
var synced = new Map;
|
|
30315
|
+
var MAX_PATHS = 400;
|
|
30316
|
+
async function treeNow(wtPath, head, dirty) {
|
|
30317
|
+
let was = synced.get(wtPath);
|
|
30318
|
+
let index = was?.index;
|
|
30319
|
+
if (!index) {
|
|
30320
|
+
const dir = await git(wtPath, "rev-parse", "--absolute-git-dir");
|
|
30321
|
+
if (!dir.ok || !dir.out)
|
|
30322
|
+
return null;
|
|
30323
|
+
index = join24(dir.out, "toyon-turn-index");
|
|
30324
|
+
}
|
|
30325
|
+
const env = { GIT_INDEX_FILE: index };
|
|
30326
|
+
const writeTree = async () => {
|
|
30327
|
+
const tree = await run(GIT, ["write-tree"], wtPath, env);
|
|
30328
|
+
return tree.ok && tree.out ? tree.out : null;
|
|
30329
|
+
};
|
|
30330
|
+
if (!existsSync27(index))
|
|
30331
|
+
was = undefined;
|
|
30332
|
+
const paths = dirty && !dirty.some((f) => /[RC]/.test(f.xy)) ? dirty.map((f) => f.path) : null;
|
|
30333
|
+
if (was && paths && was.head === head) {
|
|
30334
|
+
const again = [...new Set([...was.dirty, ...paths])];
|
|
30335
|
+
if (again.length <= MAX_PATHS) {
|
|
30336
|
+
const added = again.length === 0 || (await run(GIT, ["--literal-pathspecs", "add", "-A", "--", ...again], wtPath, env)).ok;
|
|
30337
|
+
const tree = added ? await writeTree() : null;
|
|
30338
|
+
if (tree) {
|
|
30339
|
+
synced.set(wtPath, { index, head, dirty: new Set(paths) });
|
|
30340
|
+
return tree;
|
|
30341
|
+
}
|
|
30342
|
+
}
|
|
30343
|
+
}
|
|
30344
|
+
synced.delete(wtPath);
|
|
30345
|
+
if (!existsSync27(index)) {
|
|
30346
|
+
const own = join24(index, "..", "index");
|
|
30347
|
+
if (existsSync27(own))
|
|
30348
|
+
copyFileSync(own, index);
|
|
30349
|
+
else if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
|
|
30350
|
+
return null;
|
|
30351
|
+
}
|
|
30352
|
+
const full = async () => (await run(GIT, ["add", "-A"], wtPath, env)).ok ? writeTree() : null;
|
|
30353
|
+
let tree = await full();
|
|
30354
|
+
if (!tree) {
|
|
30355
|
+
rmSync5(index, { force: true });
|
|
30356
|
+
if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
|
|
30357
|
+
return null;
|
|
30358
|
+
tree = await full();
|
|
30359
|
+
if (!tree)
|
|
30360
|
+
return null;
|
|
30361
|
+
}
|
|
30362
|
+
const differs = await git(wtPath, "diff", "--name-only", "--no-renames", "-z", head, tree);
|
|
30363
|
+
if (differs.ok)
|
|
30364
|
+
synced.set(wtPath, { index, head, dirty: new Set(differs.out.split("\x00").filter(Boolean)) });
|
|
30365
|
+
return tree;
|
|
30366
|
+
}
|
|
30367
|
+
async function snapshotAt(wtPath, ref) {
|
|
30368
|
+
const r = await git(wtPath, "rev-parse", "--quiet", `${ref}^{commit}`, `${ref}^{tree}`, `${ref}^`);
|
|
30369
|
+
const [commit, tree, parent] = r.ok ? r.out.split(`
|
|
30370
|
+
`) : [];
|
|
30371
|
+
return commit && tree && parent ? { commit, tree, parent } : null;
|
|
30372
|
+
}
|
|
30373
|
+
function markTurn(wtPath, worktreeId) {
|
|
30374
|
+
return inTurn(wtPath, async () => {
|
|
30375
|
+
const head = await git(wtPath, "rev-parse", "--verify", "HEAD");
|
|
30376
|
+
if (!head.ok)
|
|
30377
|
+
return;
|
|
30378
|
+
const tree = await treeNow(wtPath, head.out);
|
|
30379
|
+
if (!tree)
|
|
30380
|
+
return;
|
|
30381
|
+
const at = await snapshotAt(wtPath, turnRef(worktreeId, "at"));
|
|
30382
|
+
if (at?.tree === tree && at.parent === head.out)
|
|
30383
|
+
return;
|
|
30384
|
+
const commit = await git(wtPath, "-c", "user.name=toyon", "-c", "user.email=toyon@localhost", "commit-tree", tree, "-p", head.out, "-m", SNAPSHOT_SUBJECT2);
|
|
30385
|
+
if (!commit.ok)
|
|
30386
|
+
return;
|
|
30387
|
+
if (at && at.tree !== tree)
|
|
30388
|
+
await git(wtPath, "update-ref", turnRef(worktreeId, "before"), at.commit);
|
|
30389
|
+
await git(wtPath, "update-ref", turnRef(worktreeId, "at"), commit.out);
|
|
30390
|
+
});
|
|
30391
|
+
}
|
|
30392
|
+
function turnChanges(wtPath, worktreeId, defaultBr, own, dirty) {
|
|
30393
|
+
return inTurn(wtPath, async () => {
|
|
30394
|
+
const at = await snapshotAt(wtPath, turnRef(worktreeId, "at"));
|
|
30395
|
+
if (!at)
|
|
30396
|
+
return null;
|
|
30397
|
+
const head = await git(wtPath, "rev-parse", "HEAD", "HEAD^{tree}");
|
|
30398
|
+
const [headCommit, headTree] = head.ok ? head.out.split(`
|
|
30399
|
+
`) : [];
|
|
30400
|
+
if (!headCommit)
|
|
30401
|
+
return null;
|
|
30402
|
+
const tree = await treeNow(wtPath, headCommit, dirty);
|
|
30403
|
+
if (!tree)
|
|
30404
|
+
return null;
|
|
30405
|
+
const from = at.tree !== tree ? at : await snapshotAt(wtPath, turnRef(worktreeId, "before"));
|
|
30406
|
+
if (!from || from.tree === tree || from.tree === headTree)
|
|
30407
|
+
return null;
|
|
30408
|
+
if (from.parent !== headCommit) {
|
|
30409
|
+
if (!own)
|
|
30410
|
+
return null;
|
|
30411
|
+
const [then, now] = await Promise.all([
|
|
30412
|
+
git(wtPath, "merge-base", from.parent, defaultBr),
|
|
30413
|
+
git(wtPath, "merge-base", headCommit, defaultBr)
|
|
30414
|
+
]);
|
|
30415
|
+
if (!then.ok || !now.ok || then.out !== now.out)
|
|
30416
|
+
return null;
|
|
30417
|
+
}
|
|
30418
|
+
const files = await filesBetween(wtPath, from.commit, tree);
|
|
30419
|
+
return files.length > 0 ? { base: from.commit, files } : null;
|
|
30420
|
+
});
|
|
30421
|
+
}
|
|
30422
|
+
async function dropTurnRefs(repoPath, worktreeId) {
|
|
30423
|
+
for (const which of ["at", "before"])
|
|
30424
|
+
await git(repoPath, "update-ref", "-d", turnRef(worktreeId, which));
|
|
30425
|
+
}
|
|
30426
|
+
|
|
29186
30427
|
// ../daemon/src/worktrees/archive.ts
|
|
29187
|
-
import { existsSync as
|
|
29188
|
-
import { basename as basename8, dirname as dirname10, join as
|
|
30428
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync17, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
30429
|
+
import { basename as basename8, dirname as dirname10, join as join25 } from "path";
|
|
29189
30430
|
class WorktreeArchive {
|
|
29190
30431
|
dir;
|
|
29191
30432
|
records = new Map;
|
|
29192
30433
|
constructor(dir) {
|
|
29193
30434
|
this.dir = dir;
|
|
29194
|
-
|
|
29195
|
-
for (const id of
|
|
30435
|
+
mkdirSync8(dir, { recursive: true });
|
|
30436
|
+
for (const id of readdirSync8(dir)) {
|
|
29196
30437
|
const file = this.filesOf(id).record;
|
|
29197
|
-
if (!
|
|
30438
|
+
if (!existsSync28(file))
|
|
29198
30439
|
continue;
|
|
29199
30440
|
try {
|
|
29200
30441
|
this.records.set(id, JSON.parse(readFileSync17(file, "utf8")));
|
|
@@ -29208,7 +30449,7 @@ class WorktreeArchive {
|
|
|
29208
30449
|
}
|
|
29209
30450
|
put(rec, from) {
|
|
29210
30451
|
const at = this.filesOf(rec.worktree.id);
|
|
29211
|
-
|
|
30452
|
+
mkdirSync8(at.dir, { recursive: true });
|
|
29212
30453
|
move(from.transcript, at.transcript);
|
|
29213
30454
|
move(from.attachments, at.attachments);
|
|
29214
30455
|
move(from.plans, at.plans);
|
|
@@ -29228,7 +30469,7 @@ class WorktreeArchive {
|
|
|
29228
30469
|
delete(id) {
|
|
29229
30470
|
if (!this.records.has(id))
|
|
29230
30471
|
return;
|
|
29231
|
-
|
|
30472
|
+
rmSync6(this.filesOf(id).dir, { recursive: true, force: true });
|
|
29232
30473
|
this.records.delete(id);
|
|
29233
30474
|
}
|
|
29234
30475
|
list(repo) {
|
|
@@ -29243,8 +30484,8 @@ class WorktreeArchive {
|
|
|
29243
30484
|
planFile(id, rel) {
|
|
29244
30485
|
if (!this.records.has(id) || !isPlanPath(rel))
|
|
29245
30486
|
return null;
|
|
29246
|
-
const file =
|
|
29247
|
-
if (!
|
|
30487
|
+
const file = join25(this.filesOf(id).plans, basename8(rel));
|
|
30488
|
+
if (!existsSync28(file))
|
|
29248
30489
|
return null;
|
|
29249
30490
|
try {
|
|
29250
30491
|
return readFileSync17(file, "utf8");
|
|
@@ -29254,13 +30495,13 @@ class WorktreeArchive {
|
|
|
29254
30495
|
}
|
|
29255
30496
|
}
|
|
29256
30497
|
filesOf(id) {
|
|
29257
|
-
const dir =
|
|
30498
|
+
const dir = join25(this.dir, id);
|
|
29258
30499
|
return {
|
|
29259
30500
|
dir,
|
|
29260
|
-
record:
|
|
29261
|
-
transcript:
|
|
29262
|
-
attachments:
|
|
29263
|
-
plans:
|
|
30501
|
+
record: join25(dir, "record.json"),
|
|
30502
|
+
transcript: join25(dir, "transcript.jsonl"),
|
|
30503
|
+
attachments: join25(dir, "attachments"),
|
|
30504
|
+
plans: join25(dir, "plans")
|
|
29264
30505
|
};
|
|
29265
30506
|
}
|
|
29266
30507
|
}
|
|
@@ -29285,7 +30526,7 @@ function summarize(r, repoId, transcript) {
|
|
|
29285
30526
|
};
|
|
29286
30527
|
}
|
|
29287
30528
|
function lastUsage(transcript) {
|
|
29288
|
-
if (!
|
|
30529
|
+
if (!existsSync28(transcript))
|
|
29289
30530
|
return null;
|
|
29290
30531
|
const lines = readFileSync17(transcript, "utf8").split(`
|
|
29291
30532
|
`);
|
|
@@ -29301,7 +30542,7 @@ function lastUsage(transcript) {
|
|
|
29301
30542
|
return null;
|
|
29302
30543
|
}
|
|
29303
30544
|
function firstPrompt(transcript) {
|
|
29304
|
-
if (!
|
|
30545
|
+
if (!existsSync28(transcript))
|
|
29305
30546
|
return;
|
|
29306
30547
|
for (const line of readFileSync17(transcript, "utf8").split(`
|
|
29307
30548
|
`)) {
|
|
@@ -29316,10 +30557,10 @@ function firstPrompt(transcript) {
|
|
|
29316
30557
|
return;
|
|
29317
30558
|
}
|
|
29318
30559
|
function move(from, to) {
|
|
29319
|
-
if (!
|
|
30560
|
+
if (!existsSync28(from))
|
|
29320
30561
|
return;
|
|
29321
|
-
|
|
29322
|
-
|
|
30562
|
+
mkdirSync8(dirname10(to), { recursive: true });
|
|
30563
|
+
rmSync6(to, { recursive: true, force: true });
|
|
29323
30564
|
renameSync4(from, to);
|
|
29324
30565
|
}
|
|
29325
30566
|
|
|
@@ -29381,8 +30622,8 @@ class ArchivedGit {
|
|
|
29381
30622
|
}
|
|
29382
30623
|
|
|
29383
30624
|
// ../daemon/src/worktrees/spare.ts
|
|
29384
|
-
import { existsSync as
|
|
29385
|
-
import { basename as basename9, join as
|
|
30625
|
+
import { existsSync as existsSync29 } from "fs";
|
|
30626
|
+
import { basename as basename9, join as join26 } from "path";
|
|
29386
30627
|
class SparePool {
|
|
29387
30628
|
d;
|
|
29388
30629
|
inflight = new Map;
|
|
@@ -29401,7 +30642,7 @@ class SparePool {
|
|
|
29401
30642
|
if (!text.trim())
|
|
29402
30643
|
return;
|
|
29403
30644
|
const wt = this.d.state.worktree(boxId);
|
|
29404
|
-
if (wt?.kind !== "spare" || !
|
|
30645
|
+
if (wt?.kind !== "spare" || !existsSync29(wt.path))
|
|
29405
30646
|
return;
|
|
29406
30647
|
const agent = this.d.runtime.ensureAgent(wt).agent;
|
|
29407
30648
|
if (agent.runningAgent === null)
|
|
@@ -29446,11 +30687,11 @@ class SparePool {
|
|
|
29446
30687
|
return this.record(repoId, repo).then((wt) => this.warmUp(wt, repo, {}));
|
|
29447
30688
|
}
|
|
29448
30689
|
async record(repoId, repo) {
|
|
29449
|
-
const { id, dir } = freeSlot(
|
|
30690
|
+
const { id, dir } = freeSlot(join26(this.d.paths.worktreesDir, repo.name));
|
|
29450
30691
|
const wt = {
|
|
29451
30692
|
id,
|
|
29452
30693
|
repoId,
|
|
29453
|
-
path:
|
|
30694
|
+
path: join26(this.d.paths.worktreesDir, repo.name, dir),
|
|
29454
30695
|
branch: repo.defaultBranch,
|
|
29455
30696
|
kind: "spare",
|
|
29456
30697
|
phase: "reserved",
|
|
@@ -29465,7 +30706,7 @@ class SparePool {
|
|
|
29465
30706
|
const repoId = repo.id;
|
|
29466
30707
|
let cut = () => {};
|
|
29467
30708
|
const added = (async () => {
|
|
29468
|
-
if (
|
|
30709
|
+
if (existsSync29(wt.path))
|
|
29469
30710
|
return;
|
|
29470
30711
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", wt.path, baseOf(repo)));
|
|
29471
30712
|
})();
|
|
@@ -29513,7 +30754,7 @@ class SparePool {
|
|
|
29513
30754
|
}
|
|
29514
30755
|
log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
|
|
29515
30756
|
this.d.carryDraft?.(wt.id, wt.repoId);
|
|
29516
|
-
if (
|
|
30757
|
+
if (existsSync29(wt.path)) {
|
|
29517
30758
|
await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
|
|
29518
30759
|
return;
|
|
29519
30760
|
}
|
|
@@ -29570,7 +30811,7 @@ class SparePool {
|
|
|
29570
30811
|
const running = this.inflight.get(repoId);
|
|
29571
30812
|
await running?.added.catch(() => {});
|
|
29572
30813
|
running?.cut();
|
|
29573
|
-
if (wt.kind !== "spare" || !
|
|
30814
|
+
if (wt.kind !== "spare" || !existsSync29(wt.path))
|
|
29574
30815
|
return null;
|
|
29575
30816
|
}
|
|
29576
30817
|
await this.refreshing.get(repoId);
|
|
@@ -29616,7 +30857,7 @@ class Trunk {
|
|
|
29616
30857
|
const last = this.lastFetch.get(path) ?? 0;
|
|
29617
30858
|
if (Date.now() - last > FETCH_EVERY_MS) {
|
|
29618
30859
|
this.lastFetch.set(path, Date.now());
|
|
29619
|
-
fireAndForget("fetch", run(GIT, ["fetch", "--quiet"], path, NO_PROMPT).then(async (r) => {
|
|
30860
|
+
fireAndForget("fetch", withFetchLock(path, () => run(GIT, ["fetch", "--quiet"], path, NO_PROMPT)).then(async (r) => {
|
|
29620
30861
|
const main = this.d.state.worktree(mainId);
|
|
29621
30862
|
if (main)
|
|
29622
30863
|
this.noteFetch(main.repoId, r);
|
|
@@ -29641,7 +30882,7 @@ class Trunk {
|
|
|
29641
30882
|
if (Date.now() - last < OPEN_FETCH_MIN_MS)
|
|
29642
30883
|
return;
|
|
29643
30884
|
this.lastFetch.set(repo.path, Date.now());
|
|
29644
|
-
const f = await run(GIT, ["fetch", "--quiet"], repo.path, NO_PROMPT);
|
|
30885
|
+
const f = await withFetchLock(repo.path, () => run(GIT, ["fetch", "--quiet"], repo.path, NO_PROMPT));
|
|
29645
30886
|
this.noteFetch(repo.id, f);
|
|
29646
30887
|
if (!f.ok) {
|
|
29647
30888
|
log.warn("fetch", `could not fetch ${repo.path}: ${f.err.slice(0, 200)}`);
|
|
@@ -29665,7 +30906,7 @@ class Trunk {
|
|
|
29665
30906
|
return { ok: false, message: "no main checkout to pull" };
|
|
29666
30907
|
this.lastFetch.set(repo.path, Date.now());
|
|
29667
30908
|
w.step(`pulling ${repo.defaultBranch} from origin`);
|
|
29668
|
-
const f = await w.git(repo.path, ["fetch", "--quiet"]);
|
|
30909
|
+
const f = await withFetchLock(repo.path, () => w.git(repo.path, ["fetch", "--quiet"]));
|
|
29669
30910
|
this.noteFetch(repo.id, f);
|
|
29670
30911
|
if (!f.ok)
|
|
29671
30912
|
return { ok: false, message: refused("fetch failed", f) };
|
|
@@ -29807,6 +31048,7 @@ function stopOf(prev, next) {
|
|
|
29807
31048
|
// ../daemon/src/worktrees/service.ts
|
|
29808
31049
|
var LOCAL_CONFIG_FILES = [".env", ".env.local", ".env.development", ".env.development.local", ".dev.vars"];
|
|
29809
31050
|
var BASE_IGNORE = "base .gitignore";
|
|
31051
|
+
var REFUSAL_CHARS = 400;
|
|
29810
31052
|
async function deleteTree(path) {
|
|
29811
31053
|
try {
|
|
29812
31054
|
await rm4(path, { recursive: true, force: true });
|
|
@@ -29867,10 +31109,9 @@ class WorktreeService {
|
|
|
29867
31109
|
await this.discardWorktree(id);
|
|
29868
31110
|
},
|
|
29869
31111
|
carryDraft: (fromId, repoId) => {
|
|
29870
|
-
const text = this.d.drafts?.text(fromId);
|
|
29871
31112
|
const main = this.mainOf(repoId);
|
|
29872
|
-
if (
|
|
29873
|
-
this.d.drafts?.
|
|
31113
|
+
if (main)
|
|
31114
|
+
this.d.drafts?.move(fromId, main.id, true);
|
|
29874
31115
|
},
|
|
29875
31116
|
ready: (repoId) => {
|
|
29876
31117
|
const main = this.mainOf(repoId);
|
|
@@ -29879,15 +31120,15 @@ class WorktreeService {
|
|
|
29879
31120
|
if (this.d.runtime.get(main.id)?.procs) {
|
|
29880
31121
|
fireAndForget(main.id, this.d.runtime.stopPage(main.id), "main's page stops for the spare");
|
|
29881
31122
|
}
|
|
29882
|
-
const text = this.d.drafts?.text(main.id);
|
|
29883
31123
|
const spare = this.leadSpareOf(repoId);
|
|
29884
|
-
if (
|
|
29885
|
-
this.d.drafts?.
|
|
29886
|
-
this.d.drafts?.drop(main.id);
|
|
29887
|
-
}
|
|
31124
|
+
if (spare)
|
|
31125
|
+
this.d.drafts?.move(main.id, spare);
|
|
29888
31126
|
}
|
|
29889
31127
|
});
|
|
29890
31128
|
d.hub.on("agent", (worktreeId, _seq, event) => {
|
|
31129
|
+
if ((event.type === "tool-start" || event.type === "tool-update" && event.kind) && isWriteTool({ name: event.name ?? "", kind: event.kind })) {
|
|
31130
|
+
this.startPreview(worktreeId);
|
|
31131
|
+
}
|
|
29891
31132
|
if (event.type !== "usage")
|
|
29892
31133
|
return;
|
|
29893
31134
|
const { used, size, cost } = event;
|
|
@@ -29952,14 +31193,14 @@ class WorktreeService {
|
|
|
29952
31193
|
rt.agent.send(agentPrompt, { context: withCarry(context, carried), attachments });
|
|
29953
31194
|
this.scheduleNaming(claimed, task, variant);
|
|
29954
31195
|
if (carried.unmoved)
|
|
29955
|
-
throw new UserError(carried.unmoved);
|
|
31196
|
+
throw new UserError(carried.unmoved, { delivered: true });
|
|
29956
31197
|
return claimed;
|
|
29957
31198
|
}
|
|
29958
31199
|
if (!await isGitRepo(repo.path)) {
|
|
29959
31200
|
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
31201
|
}
|
|
29961
|
-
const { id, dir } = freeSlot(
|
|
29962
|
-
const wtPath =
|
|
31202
|
+
const { id, dir } = freeSlot(join27(this.d.paths.worktreesDir, repo.name));
|
|
31203
|
+
const wtPath = join27(this.d.paths.worktreesDir, repo.name, dir);
|
|
29963
31204
|
const branch = `toyon/${dir}`;
|
|
29964
31205
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
|
|
29965
31206
|
const wt = {
|
|
@@ -29986,7 +31227,7 @@ class WorktreeService {
|
|
|
29986
31227
|
this.d.runtime.ensureAgent(wt).agent.send(agentPrompt, { context: withCarry(context, carried), attachments });
|
|
29987
31228
|
this.scheduleNaming(wt, task, variant);
|
|
29988
31229
|
if (carried.unmoved)
|
|
29989
|
-
throw new UserError(carried.unmoved);
|
|
31230
|
+
throw new UserError(carried.unmoved, { delivered: true });
|
|
29990
31231
|
return wt;
|
|
29991
31232
|
}
|
|
29992
31233
|
async carryMain(repo, wt, move) {
|
|
@@ -30025,7 +31266,7 @@ class WorktreeService {
|
|
|
30025
31266
|
};
|
|
30026
31267
|
const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
|
|
30027
31268
|
const restored = (carried.ok ? carried.out.split(`
|
|
30028
|
-
`).filter(Boolean) : []).filter((p) => !
|
|
31269
|
+
`).filter(Boolean) : []).filter((p) => !existsSync30(join27(wt.path, p)));
|
|
30029
31270
|
const applied = await git(wt.path, "stash", "apply", sha);
|
|
30030
31271
|
if (applied.ok) {
|
|
30031
31272
|
const ref = await entry();
|
|
@@ -30061,9 +31302,9 @@ class WorktreeService {
|
|
|
30061
31302
|
const branch = kind === "pr" ? `pr/${number}` : ref;
|
|
30062
31303
|
const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
|
|
30063
31304
|
let slug = branchSlug(title) || "branch";
|
|
30064
|
-
if (
|
|
31305
|
+
if (existsSync30(join27(this.d.paths.worktreesDir, repo.name, slug)))
|
|
30065
31306
|
slug = `${slug}-${shortId().slice(0, 4)}`;
|
|
30066
|
-
const wtPath =
|
|
31307
|
+
const wtPath = join27(this.d.paths.worktreesDir, repo.name, slug);
|
|
30067
31308
|
await withRepoLock(repo.path, async () => {
|
|
30068
31309
|
const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
|
|
30069
31310
|
if (out)
|
|
@@ -30106,7 +31347,7 @@ class WorktreeService {
|
|
|
30106
31347
|
throw new UserError("that worktree is gone, or Toyon already has it");
|
|
30107
31348
|
return found;
|
|
30108
31349
|
}
|
|
30109
|
-
async adopt(worktreeId, createdBy) {
|
|
31350
|
+
async adopt(worktreeId, createdBy, message) {
|
|
30110
31351
|
const r = this.readable(worktreeId);
|
|
30111
31352
|
if (!r)
|
|
30112
31353
|
throw new UserError("that worktree is gone");
|
|
@@ -30135,12 +31376,18 @@ class WorktreeService {
|
|
|
30135
31376
|
title: found.name,
|
|
30136
31377
|
createdAt: Date.now(),
|
|
30137
31378
|
agent: this.d.agents.require(this.d.state.defaultAgent ?? DEFAULT_AGENT_ID).id,
|
|
31379
|
+
parked: true,
|
|
31380
|
+
...message ? { promptedAt: Date.now() } : {},
|
|
30138
31381
|
...createdBy ? { createdBy } : {}
|
|
30139
31382
|
};
|
|
30140
31383
|
return rec;
|
|
30141
31384
|
});
|
|
30142
31385
|
this.invalidateDiscovered();
|
|
30143
31386
|
this.launch(wt, repo, repo.path, { setupCommands: false });
|
|
31387
|
+
const { agent } = this.d.runtime.ensureAgent(wt);
|
|
31388
|
+
agent.note({ type: "adopted", branch: wt.branch, ts: Date.now() });
|
|
31389
|
+
if (message)
|
|
31390
|
+
agent.send(message.text, { attachments: message.attachments });
|
|
30144
31391
|
return wt;
|
|
30145
31392
|
}
|
|
30146
31393
|
async ownProcs(worktreeId, names) {
|
|
@@ -30283,7 +31530,7 @@ class WorktreeService {
|
|
|
30283
31530
|
try {
|
|
30284
31531
|
await this.removeDirectory(repo, wt);
|
|
30285
31532
|
} catch (e) {
|
|
30286
|
-
|
|
31533
|
+
rmSync7(this.plansScratch(wt.id), { recursive: true, force: true });
|
|
30287
31534
|
throw e;
|
|
30288
31535
|
}
|
|
30289
31536
|
if (hasOwnBranch(wt)) {
|
|
@@ -30297,6 +31544,7 @@ class WorktreeService {
|
|
|
30297
31544
|
this.d.state.removeWorktree(worktreeId);
|
|
30298
31545
|
releasePort(wt.proxyPort);
|
|
30299
31546
|
let archived = null;
|
|
31547
|
+
await dropTurnRefs(repo.path, worktreeId);
|
|
30300
31548
|
if (archive)
|
|
30301
31549
|
archived = this.archiveChat(wt, repo, kept, sessionId, reason);
|
|
30302
31550
|
else {
|
|
@@ -30309,7 +31557,7 @@ class WorktreeService {
|
|
|
30309
31557
|
}
|
|
30310
31558
|
async teardown(wt, repo) {
|
|
30311
31559
|
const cmds = repo.config.teardown ?? [];
|
|
30312
|
-
if (cmds.length === 0 || !
|
|
31560
|
+
if (cmds.length === 0 || !existsSync30(wt.path))
|
|
30313
31561
|
return;
|
|
30314
31562
|
for (const cmd of cmds) {
|
|
30315
31563
|
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 +31566,7 @@ class WorktreeService {
|
|
|
30318
31566
|
}
|
|
30319
31567
|
}
|
|
30320
31568
|
async keep(repo, wt) {
|
|
30321
|
-
const index =
|
|
31569
|
+
const index = join27(this.d.paths.archiveDir, `${wt.id}.index`);
|
|
30322
31570
|
const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
|
|
30323
31571
|
if (!kept)
|
|
30324
31572
|
log.warn(wt.id, "could not keep its git state");
|
|
@@ -30355,11 +31603,11 @@ class WorktreeService {
|
|
|
30355
31603
|
log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
|
|
30356
31604
|
}
|
|
30357
31605
|
plansScratch(worktreeId) {
|
|
30358
|
-
return
|
|
31606
|
+
return join27(this.d.paths.archiveDir, `${worktreeId}.plans`);
|
|
30359
31607
|
}
|
|
30360
31608
|
keepPlans(wt) {
|
|
30361
|
-
const from =
|
|
30362
|
-
if (!
|
|
31609
|
+
const from = join27(wt.path, PLANS_DIR);
|
|
31610
|
+
if (!existsSync30(from))
|
|
30363
31611
|
return;
|
|
30364
31612
|
try {
|
|
30365
31613
|
cpSync(from, this.plansScratch(wt.id), { recursive: true });
|
|
@@ -30400,8 +31648,8 @@ class WorktreeService {
|
|
|
30400
31648
|
deleteChat(worktreeId) {
|
|
30401
31649
|
const files = this.chatFiles(worktreeId);
|
|
30402
31650
|
try {
|
|
30403
|
-
|
|
30404
|
-
|
|
31651
|
+
rmSync7(files.transcript, { force: true });
|
|
31652
|
+
rmSync7(files.attachments, { recursive: true, force: true });
|
|
30405
31653
|
} catch (e) {
|
|
30406
31654
|
log.warn(worktreeId, "could not delete transcript or attachments", e);
|
|
30407
31655
|
}
|
|
@@ -30447,7 +31695,7 @@ class WorktreeService {
|
|
|
30447
31695
|
}
|
|
30448
31696
|
archivedAttachment(archiveId, file) {
|
|
30449
31697
|
const files = this.archive.chatFiles(archiveId);
|
|
30450
|
-
return files && isAttachmentFile(file) ?
|
|
31698
|
+
return files && isAttachmentFile(file) ? join27(files.attachments, file) : null;
|
|
30451
31699
|
}
|
|
30452
31700
|
archivedGit(archiveId) {
|
|
30453
31701
|
const rec = this.archive.get(archiveId);
|
|
@@ -30490,7 +31738,7 @@ class WorktreeService {
|
|
|
30490
31738
|
throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
|
|
30491
31739
|
if (rec.auto)
|
|
30492
31740
|
log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
|
|
30493
|
-
const path =
|
|
31741
|
+
const path = existsSync30(old.path) ? join27(dirname11(old.path), freeSlot(dirname11(old.path)).dir) : old.path;
|
|
30494
31742
|
const branch = await withRepoLock(repo.path, async () => {
|
|
30495
31743
|
if (!await commitOf(repo.path, archiveRef(old.id))) {
|
|
30496
31744
|
throw new UserError(`${old.title}'s kept commits are gone from git`);
|
|
@@ -30524,10 +31772,11 @@ class WorktreeService {
|
|
|
30524
31772
|
branch,
|
|
30525
31773
|
proxyPort: await allocateProxyPort(),
|
|
30526
31774
|
promptedAt: Date.now(),
|
|
31775
|
+
parked: true,
|
|
30527
31776
|
...createdBy ? { createdBy } : {}
|
|
30528
31777
|
};
|
|
30529
31778
|
try {
|
|
30530
|
-
this.archive.take(old.id, { ...this.chatFiles(old.id), plans:
|
|
31779
|
+
this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join27(path, PLANS_DIR) });
|
|
30531
31780
|
} catch (e) {
|
|
30532
31781
|
log.warn(old.id, "could not move its chat back out of the archive", e);
|
|
30533
31782
|
}
|
|
@@ -30556,12 +31805,12 @@ class WorktreeService {
|
|
|
30556
31805
|
return;
|
|
30557
31806
|
const repo = this.repoOf(rec);
|
|
30558
31807
|
const repoPath = repo?.path ?? rec.repoPath;
|
|
30559
|
-
if (rec.kept &&
|
|
31808
|
+
if (rec.kept && existsSync30(repoPath)) {
|
|
30560
31809
|
const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
|
|
30561
31810
|
if (!r.ok)
|
|
30562
31811
|
log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
|
|
30563
31812
|
}
|
|
30564
|
-
if (rec.worktree.lands?.length &&
|
|
31813
|
+
if (rec.worktree.lands?.length && existsSync30(repoPath))
|
|
30565
31814
|
await dropLandRefs(repoPath, archiveId);
|
|
30566
31815
|
this.archive.delete(archiveId);
|
|
30567
31816
|
this.d.drafts?.drop(archiveId);
|
|
@@ -30652,6 +31901,15 @@ class WorktreeService {
|
|
|
30652
31901
|
this.d.hub.emit("worktreesChanged");
|
|
30653
31902
|
return { target, grafted: sources.map((w) => w.title) };
|
|
30654
31903
|
}
|
|
31904
|
+
startPreview(worktreeId) {
|
|
31905
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
31906
|
+
if (!wt?.parked)
|
|
31907
|
+
return;
|
|
31908
|
+
delete wt.parked;
|
|
31909
|
+
this.d.state.save();
|
|
31910
|
+
this.d.hub.emit("worktreesChanged");
|
|
31911
|
+
fireAndForget(worktreeId, this.d.runtime.wake(worktreeId), "start a parked preview");
|
|
31912
|
+
}
|
|
30655
31913
|
async setupAndStart(wt, repo, depsSource = repo.path, opts = {}) {
|
|
30656
31914
|
this.d.runtime.markSetup(wt.id, true);
|
|
30657
31915
|
try {
|
|
@@ -30661,9 +31919,9 @@ class WorktreeService {
|
|
|
30661
31919
|
}
|
|
30662
31920
|
}
|
|
30663
31921
|
async mirrorBaseIgnore(repo) {
|
|
30664
|
-
const src =
|
|
30665
|
-
const tracked =
|
|
30666
|
-
const mirror =
|
|
31922
|
+
const src = join27(repo.path, ".gitignore");
|
|
31923
|
+
const tracked = existsSync30(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
|
|
31924
|
+
const mirror = existsSync30(src) && !tracked;
|
|
30667
31925
|
const lines = mirror ? readFileSync18(src, "utf8").split(`
|
|
30668
31926
|
`).filter((l) => l.trim() !== "") : [];
|
|
30669
31927
|
await excludeBlock(repo.path, BASE_IGNORE, lines);
|
|
@@ -30676,9 +31934,9 @@ class WorktreeService {
|
|
|
30676
31934
|
await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
|
|
30677
31935
|
run.finish(0);
|
|
30678
31936
|
}
|
|
30679
|
-
const srcNm =
|
|
30680
|
-
const dstNm =
|
|
30681
|
-
if (
|
|
31937
|
+
const srcNm = join27(depsSource, "node_modules");
|
|
31938
|
+
const dstNm = join27(wt.path, "node_modules");
|
|
31939
|
+
if (existsSync30(srcNm) && !existsSync30(dstNm)) {
|
|
30682
31940
|
const r = await cloneTree(srcNm, dstNm);
|
|
30683
31941
|
if (r.ok)
|
|
30684
31942
|
this.d.hub.emit("log", wt.id, "setup", `deps via ${r.how} in ${r.ms}ms`);
|
|
@@ -30686,10 +31944,10 @@ class WorktreeService {
|
|
|
30686
31944
|
log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
|
|
30687
31945
|
}
|
|
30688
31946
|
for (const f of LOCAL_CONFIG_FILES) {
|
|
30689
|
-
const src =
|
|
30690
|
-
if (!
|
|
31947
|
+
const src = join27(depsSource, f);
|
|
31948
|
+
if (!existsSync30(src) || existsSync30(join27(wt.path, f)))
|
|
30691
31949
|
continue;
|
|
30692
|
-
const r = await run("cp", [src,
|
|
31950
|
+
const r = await run("cp", [src, join27(wt.path, f)], wt.path);
|
|
30693
31951
|
if (r.ok)
|
|
30694
31952
|
this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
|
|
30695
31953
|
else
|
|
@@ -30820,13 +32078,15 @@ ${wt.landing.body}` : s;
|
|
|
30820
32078
|
const handle = commit ? this.runs.begin(wt.id, "commit", { timeoutMs: ceiling }) : null;
|
|
30821
32079
|
const run = (onText, signal) => stepRun(cwd, args, onText, signal, {
|
|
30822
32080
|
timeoutMs: ceiling,
|
|
30823
|
-
...handle ? { onSpawn: handle.spawned
|
|
32081
|
+
...handle ? { onSpawn: handle.spawned } : {},
|
|
32082
|
+
onHook: (hook) => handle?.stage(hook ? `${hook} hook` : undefined)
|
|
30824
32083
|
});
|
|
30825
32084
|
const watched = async () => {
|
|
30826
32085
|
if (!this.d.watch)
|
|
30827
32086
|
return run(() => {});
|
|
32087
|
+
const command = `${where} ${commandLine(args)}`;
|
|
30828
32088
|
try {
|
|
30829
|
-
return await this.d.watch(wt.id,
|
|
32089
|
+
return { ...await this.d.watch(wt.id, command, run), command };
|
|
30830
32090
|
} catch (e) {
|
|
30831
32091
|
log.warn(wt.id, `landing step runs unwatched: ${e instanceof Error ? e.message : e}`);
|
|
30832
32092
|
return run(() => {});
|
|
@@ -30864,7 +32124,35 @@ ${wt.landing.body}` : s;
|
|
|
30864
32124
|
});
|
|
30865
32125
|
if (out.result.ok && out.archiveIds)
|
|
30866
32126
|
this.noteLanded(wt, out.result.message, out.archiveIds);
|
|
30867
|
-
return out;
|
|
32127
|
+
return { ...out, result: this.fixShip(wt, out.result, baseOf(repo)) };
|
|
32128
|
+
}
|
|
32129
|
+
fixShip(wt, result, base) {
|
|
32130
|
+
if (result.ok)
|
|
32131
|
+
return result;
|
|
32132
|
+
if (result.hook?.name === "commit-msg") {
|
|
32133
|
+
this.d.hub.emit("messageRefused", wt.id, result.hook.text.trim().slice(-REFUSAL_CHARS));
|
|
32134
|
+
return { ...result, message: "the commit-msg hook refused the message: another is being written" };
|
|
32135
|
+
}
|
|
32136
|
+
const { hook } = result;
|
|
32137
|
+
const failure = hook ? { kind: "hook", hook: hook.name, toolId: hook.toolId, command: hook.command, text: hook.text } : result.conflict ? { kind: "conflict", base, how: result.conflict, ...result.step ? { step: result.step } : {} } : null;
|
|
32138
|
+
return failure && this.d.fix.report(wt.id, failure) ? { ...result, asked: true } : result;
|
|
32139
|
+
}
|
|
32140
|
+
async fixPreview(worktreeId, clientId) {
|
|
32141
|
+
const wt = this.d.state.requireWorktree(worktreeId);
|
|
32142
|
+
const failure = {
|
|
32143
|
+
kind: "preview",
|
|
32144
|
+
procs: this.d.runtime.get(worktreeId)?.procs?.states() ?? [],
|
|
32145
|
+
log: this.d.runtime.recentLogs(worktreeId)
|
|
32146
|
+
};
|
|
32147
|
+
if (!isLead(wt)) {
|
|
32148
|
+
if (!this.d.fix.report(worktreeId, failure, { pressed: true }))
|
|
32149
|
+
throw new UserError("worktree still starting; try again in a moment");
|
|
32150
|
+
return;
|
|
32151
|
+
}
|
|
32152
|
+
await this.create(wt.repoId, fixPrompt(failure), {
|
|
32153
|
+
...clientId ? { createdBy: clientId } : {},
|
|
32154
|
+
...isProvisional(wt) ? { worktreeId } : {}
|
|
32155
|
+
});
|
|
30868
32156
|
}
|
|
30869
32157
|
async movedMidTurn(wt) {
|
|
30870
32158
|
const status = this.d.runtime.agentFor(wt.id)?.status;
|
|
@@ -30993,11 +32281,14 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
30993
32281
|
if (committed && !committed.ok)
|
|
30994
32282
|
return committed;
|
|
30995
32283
|
committedHere = committed !== null;
|
|
32284
|
+
const method = policy.merge ?? DEFAULT_MERGE_METHOD;
|
|
32285
|
+
const merged = own && method === "rebase" ? await requireLinear(wt.path, base) : null;
|
|
32286
|
+
if (merged)
|
|
32287
|
+
return merged;
|
|
30996
32288
|
const taken = await takeMainIn(wt.path, base, own, w);
|
|
30997
32289
|
if (!taken.ok)
|
|
30998
32290
|
return taken;
|
|
30999
32291
|
const mark = await landingMark(wt.path, base);
|
|
31000
|
-
const method = policy.merge ?? DEFAULT_MERGE_METHOD;
|
|
31001
32292
|
const squash = method === "squash" ? await squashMessage(wt.path, base, suggested) : "";
|
|
31002
32293
|
const landed = await landLocally(wt.path, wt.branch, repo.path, repo.defaultBranch, method, squash, w);
|
|
31003
32294
|
if (!landed.ok)
|
|
@@ -31028,6 +32319,9 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31028
32319
|
return { result: { ok: false, message: `nothing to land: no commits ahead of ${base}` } };
|
|
31029
32320
|
let sha = "";
|
|
31030
32321
|
let already = false;
|
|
32322
|
+
const merged = own && method === "rebase" ? await requireLinear(wt.path, base) : null;
|
|
32323
|
+
if (merged)
|
|
32324
|
+
return { result: merged };
|
|
31031
32325
|
for (let attempt = 0;; attempt++) {
|
|
31032
32326
|
const fetched = await this.fetchBase(wt, repo, w);
|
|
31033
32327
|
if (!fetched.ok)
|
|
@@ -31097,7 +32391,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31097
32391
|
async fetchBase(wt, repo, w) {
|
|
31098
32392
|
if (!baseIsRemote(repo))
|
|
31099
32393
|
return { ok: true, message: `${baseOf(repo)} here is the base` };
|
|
31100
|
-
const r = await fetchBase(wt.path, repo, w);
|
|
32394
|
+
const r = await withFetchLock(repo.path, () => fetchBase(wt.path, repo, w));
|
|
31101
32395
|
this.trunk.noteFetch(repo.id, { ok: r.ok, err: r.message });
|
|
31102
32396
|
return r;
|
|
31103
32397
|
}
|
|
@@ -31165,7 +32459,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31165
32459
|
}
|
|
31166
32460
|
return taken;
|
|
31167
32461
|
});
|
|
31168
|
-
return { result, base: r.base };
|
|
32462
|
+
return { result: r.wt ? this.fixShip(r.wt, result, r.base) : result, base: r.base };
|
|
31169
32463
|
}
|
|
31170
32464
|
async pull(worktreeId) {
|
|
31171
32465
|
const wt = this.d.state.requireWorktree(worktreeId);
|
|
@@ -31187,7 +32481,8 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31187
32481
|
const m = message.trim();
|
|
31188
32482
|
if (!m)
|
|
31189
32483
|
throw new UserError("commit message required");
|
|
31190
|
-
|
|
32484
|
+
const base = baseOf(this.d.state.requireRepo(wt.repoId));
|
|
32485
|
+
return this.fixShip(wt, await this.ship(wt.id, "commit", () => this.commitRecorded(wt, m)), base);
|
|
31191
32486
|
}
|
|
31192
32487
|
setEmpty(wt, empty) {
|
|
31193
32488
|
if (wt.empty === empty)
|
|
@@ -31333,6 +32628,11 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31333
32628
|
base: baseOf(repo)
|
|
31334
32629
|
};
|
|
31335
32630
|
}
|
|
32631
|
+
async turnStarted(worktreeId) {
|
|
32632
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
32633
|
+
if (wt && !this.going.has(worktreeId))
|
|
32634
|
+
await markTurn(wt.path, wt.id);
|
|
32635
|
+
}
|
|
31336
32636
|
async gitStatus(worktreeId) {
|
|
31337
32637
|
if (this.going.has(worktreeId))
|
|
31338
32638
|
return null;
|
|
@@ -31353,6 +32653,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31353
32653
|
const counts = { ...ab, ...unpushed === undefined ? {} : { unpushed } };
|
|
31354
32654
|
const ahead = counts.ahead ?? 0;
|
|
31355
32655
|
const committed = !isMain && ahead > 0 ? await committedFiles(r.path, r.base) : undefined;
|
|
32656
|
+
const turn = r.wt && files.length > 0 ? await turnChanges(r.path, r.wt.id, r.base, !isMain, files) : null;
|
|
31356
32657
|
if (r.wt && !isMain && hasOwnBranch(r.wt) && files.length === 0 && ahead === 0 && head.ok && this.handChecked.get(r.wt.id) !== head.out) {
|
|
31357
32658
|
this.handChecked.set(r.wt.id, head.out);
|
|
31358
32659
|
const mark = await handLanding(r.path, r.wt.branch, r.base, r.wt.lands?.at(-1)?.tip);
|
|
@@ -31379,7 +32680,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
31379
32680
|
if (this.going.has(worktreeId))
|
|
31380
32681
|
return null;
|
|
31381
32682
|
this.noteCounts(worktreeId, isMain, files.length, counts);
|
|
31382
|
-
return { files, committed, head: head.ok ? head.out : undefined, ...counts };
|
|
32683
|
+
return { files, committed, ...turn ? { turn } : {}, head: head.ok ? head.out : undefined, ...counts };
|
|
31383
32684
|
} catch (e) {
|
|
31384
32685
|
log.warn(worktreeId, "git status failed", e);
|
|
31385
32686
|
return null;
|
|
@@ -31648,7 +32949,8 @@ var accounts = new AgentAccounts({
|
|
|
31648
32949
|
connect: async (app, spec) => spawnAcp(app, agents.launch(spec, await prepareLaunch(paths.scratchDir, spec)), paths.scratchDir, `auth:${spec.id}`)
|
|
31649
32950
|
});
|
|
31650
32951
|
accounts.onChange = () => hub.emit("agentsChanged");
|
|
31651
|
-
var
|
|
32952
|
+
var uploads = new UploadStore(paths.uploadsDir);
|
|
32953
|
+
var attachments2 = new AttachmentStore(paths.attachmentsDir, uploads);
|
|
31652
32954
|
var runtime = new RuntimeRegistry({
|
|
31653
32955
|
hub,
|
|
31654
32956
|
state,
|
|
@@ -31658,16 +32960,23 @@ var runtime = new RuntimeRegistry({
|
|
|
31658
32960
|
accounts,
|
|
31659
32961
|
attachments: attachments2,
|
|
31660
32962
|
bridgeScript: () => bridge.get(),
|
|
32963
|
+
turnStarting: (id) => worktrees.turnStarted(id),
|
|
31661
32964
|
remote,
|
|
31662
32965
|
grant: previewGrant(token),
|
|
31663
32966
|
viewed: (id) => idle.isViewed(id),
|
|
31664
32967
|
shown: (id) => idle.isShown(id),
|
|
31665
32968
|
mainLeads: (repoId) => worktrees.spare.current(repoId) === null
|
|
31666
32969
|
});
|
|
31667
|
-
var drafts = new DraftStore({
|
|
32970
|
+
var drafts = new DraftStore({
|
|
32971
|
+
file: paths.draftsFile,
|
|
32972
|
+
attachmentsFile: paths.draftAttachmentsFile,
|
|
32973
|
+
hub,
|
|
32974
|
+
uploads
|
|
32975
|
+
});
|
|
31668
32976
|
var runs = new RunService({ state, hub });
|
|
31669
32977
|
var exec = new ExecService({ state, runtime, shipping: (id) => worktrees.shippingOf(id) });
|
|
31670
32978
|
var cache = new ArtifactCache({ paths, state, hub });
|
|
32979
|
+
var fix = new FixService({ state, hub, runtime });
|
|
31671
32980
|
var worktrees = new WorktreeService({
|
|
31672
32981
|
state,
|
|
31673
32982
|
hub,
|
|
@@ -31677,7 +32986,8 @@ var worktrees = new WorktreeService({
|
|
|
31677
32986
|
drafts,
|
|
31678
32987
|
cache,
|
|
31679
32988
|
watch: (id, command, run) => exec.watch(id, command, run),
|
|
31680
|
-
runs
|
|
32989
|
+
runs,
|
|
32990
|
+
fix
|
|
31681
32991
|
});
|
|
31682
32992
|
new BackendShare({ state, hub, runtime, own: (id, names) => worktrees.ownProcs(id, names) });
|
|
31683
32993
|
var turns = new TurnService({
|
|
@@ -31777,6 +33087,13 @@ var idleExit = new IdleExit({
|
|
|
31777
33087
|
},
|
|
31778
33088
|
afterMs: stopAfter
|
|
31779
33089
|
});
|
|
33090
|
+
var keepAwake = new KeepAwake({
|
|
33091
|
+
hub,
|
|
33092
|
+
demand: () => runtime.demand(),
|
|
33093
|
+
assert: idleSleepAssertion(process.env.TOYON_KEEP_AWAKE),
|
|
33094
|
+
mode: () => state.keepAwake,
|
|
33095
|
+
answerable: remote !== null
|
|
33096
|
+
});
|
|
31780
33097
|
var { branded, stop: stopServer } = startServer({
|
|
31781
33098
|
port,
|
|
31782
33099
|
token,
|
|
@@ -31798,6 +33115,7 @@ var { branded, stop: stopServer } = startServer({
|
|
|
31798
33115
|
runtime,
|
|
31799
33116
|
idle,
|
|
31800
33117
|
idleExit,
|
|
33118
|
+
keepAwake,
|
|
31801
33119
|
exec,
|
|
31802
33120
|
runs,
|
|
31803
33121
|
refs,
|
|
@@ -31805,6 +33123,7 @@ var { branded, stop: stopServer } = startServer({
|
|
|
31805
33123
|
drafts,
|
|
31806
33124
|
prs,
|
|
31807
33125
|
landing,
|
|
33126
|
+
fix,
|
|
31808
33127
|
themes,
|
|
31809
33128
|
agents,
|
|
31810
33129
|
accounts,
|
|
@@ -31834,6 +33153,7 @@ var stopLagSampler = startLagSampler();
|
|
|
31834
33153
|
await runtime.reclaimOrphans();
|
|
31835
33154
|
await repos.boot();
|
|
31836
33155
|
drafts.prune((id) => !!state.worktree(id) || worktrees.hasArchived(id));
|
|
33156
|
+
uploads.sweep(drafts.uploadIds());
|
|
31837
33157
|
idle.boot();
|
|
31838
33158
|
runs.boot();
|
|
31839
33159
|
landing.boot();
|
|
@@ -31876,6 +33196,7 @@ async function shutdown(signal, opts = {}) {
|
|
|
31876
33196
|
shuttingDown = true;
|
|
31877
33197
|
log.info("daemon", `${signal}: stopping dev servers`);
|
|
31878
33198
|
idleExit.stop();
|
|
33199
|
+
keepAwake.stop();
|
|
31879
33200
|
idle.shutdown();
|
|
31880
33201
|
landing.stop();
|
|
31881
33202
|
runs.stopWatching();
|
|
@@ -31889,7 +33210,7 @@ async function shutdown(signal, opts = {}) {
|
|
|
31889
33210
|
const deadline = new Promise((resolve) => setTimeout(resolve, 5000));
|
|
31890
33211
|
await Promise.race([Promise.all([runtime.shutdown(), exec.stopAll()]), deadline]);
|
|
31891
33212
|
state.flushGroups();
|
|
31892
|
-
|
|
33213
|
+
rmSync8(paths.pidFile, { force: true });
|
|
31893
33214
|
if (opts.respawn) {
|
|
31894
33215
|
await Bun.sleep(250);
|
|
31895
33216
|
log.info("daemon", "starting the replacement");
|