toyon 0.19.0 → 0.20.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/CHANGELOG.md +33 -0
- package/dist/cli.js +1 -1
- package/dist/daemon.js +604 -91
- package/dist/shell/assets/{Editor-B-1lrUVF.js → Editor--Z8ktewC.js} +3 -3
- package/dist/shell/assets/Editor--Z8ktewC.js.br +0 -0
- package/dist/shell/assets/Editor--Z8ktewC.js.gz +0 -0
- package/dist/shell/assets/{XTerm-CZl-vWEO.js → XTerm-DsUQ0Xew.js} +1 -1
- package/dist/shell/assets/XTerm-DsUQ0Xew.js.br +0 -0
- package/dist/shell/assets/XTerm-DsUQ0Xew.js.gz +0 -0
- package/dist/shell/assets/{cssMode-BpigOakf.js → cssMode-BjAkCPrp.js} +1 -1
- package/dist/shell/assets/cssMode-BjAkCPrp.js.br +0 -0
- package/dist/shell/assets/cssMode-BjAkCPrp.js.gz +0 -0
- package/dist/shell/assets/{freemarker2-vvh2KLdJ.js → freemarker2-BZCFxXwq.js} +1 -1
- package/dist/shell/assets/freemarker2-BZCFxXwq.js.br +0 -0
- package/dist/shell/assets/freemarker2-BZCFxXwq.js.gz +0 -0
- package/dist/shell/assets/{grammar-DBMSnNcB.js → grammar-CQ1uaD6_.js} +1 -1
- package/dist/shell/assets/{grammar-DBMSnNcB.js.br → grammar-CQ1uaD6_.js.br} +0 -0
- package/dist/shell/assets/grammar-CQ1uaD6_.js.gz +0 -0
- package/dist/shell/assets/{handlebars-ExFAR1bz.js → handlebars-G1zJeDLP.js} +1 -1
- package/dist/shell/assets/handlebars-G1zJeDLP.js.br +0 -0
- package/dist/shell/assets/handlebars-G1zJeDLP.js.gz +0 -0
- package/dist/shell/assets/{html-DpIWoe_L.js → html-CUHSCFMy.js} +1 -1
- package/dist/shell/assets/html-CUHSCFMy.js.br +0 -0
- package/dist/shell/assets/html-CUHSCFMy.js.gz +0 -0
- package/dist/shell/assets/{htmlMode-vCEOKCM-.js → htmlMode-gpMedtd9.js} +1 -1
- package/dist/shell/assets/htmlMode-gpMedtd9.js.br +0 -0
- package/dist/shell/assets/htmlMode-gpMedtd9.js.gz +0 -0
- package/dist/shell/assets/index-B3Alj0WH.css +1 -0
- package/dist/shell/assets/index-B3Alj0WH.css.br +0 -0
- package/dist/shell/assets/index-B3Alj0WH.css.gz +0 -0
- package/dist/shell/assets/index-VVd_p5tJ.js +278 -0
- package/dist/shell/assets/index-VVd_p5tJ.js.br +0 -0
- package/dist/shell/assets/index-VVd_p5tJ.js.gz +0 -0
- package/dist/shell/assets/{javascript-CF6f9y1O.js → javascript-BHWH8gQN.js} +1 -1
- package/dist/shell/assets/{jsonMode-CZzpDv9U.js → jsonMode-DyV5nvVT.js} +1 -1
- package/dist/shell/assets/jsonMode-DyV5nvVT.js.br +0 -0
- package/dist/shell/assets/jsonMode-DyV5nvVT.js.gz +0 -0
- package/dist/shell/assets/{liquid-5T0ZgJhW.js → liquid-Dy3tUgRC.js} +1 -1
- package/dist/shell/assets/liquid-Dy3tUgRC.js.br +0 -0
- package/dist/shell/assets/liquid-Dy3tUgRC.js.gz +0 -0
- package/dist/shell/assets/{lspLanguageFeatures-DrkNrp6u.js → lspLanguageFeatures-CB_Ow-w5.js} +1 -1
- package/dist/shell/assets/lspLanguageFeatures-CB_Ow-w5.js.br +0 -0
- package/dist/shell/assets/lspLanguageFeatures-CB_Ow-w5.js.gz +0 -0
- package/dist/shell/assets/{mdx-DnLcCYKb.js → mdx-DD5jgco_.js} +1 -1
- package/dist/shell/assets/mdx-DD5jgco_.js.br +0 -0
- package/dist/shell/assets/mdx-DD5jgco_.js.gz +0 -0
- package/dist/shell/assets/{python-Di4WuyJe.js → python-BvLKUXll.js} +1 -1
- package/dist/shell/assets/python-BvLKUXll.js.br +0 -0
- package/dist/shell/assets/python-BvLKUXll.js.gz +0 -0
- package/dist/shell/assets/{razor-NpEB1IHs.js → razor-B2tUlbzR.js} +1 -1
- package/dist/shell/assets/razor-B2tUlbzR.js.br +0 -0
- package/dist/shell/assets/razor-B2tUlbzR.js.gz +0 -0
- package/dist/shell/assets/{tsMode-BylSuxTF.js → tsMode-DMuNnqM7.js} +1 -1
- package/dist/shell/assets/tsMode-DMuNnqM7.js.br +0 -0
- package/dist/shell/assets/tsMode-DMuNnqM7.js.gz +0 -0
- package/dist/shell/assets/{xml-BYRl5HAh.js → xml-DdYgb5a2.js} +1 -1
- package/dist/shell/assets/xml-DdYgb5a2.js.br +0 -0
- package/dist/shell/assets/xml-DdYgb5a2.js.gz +0 -0
- package/dist/shell/assets/{yaml-DeSD0TUR.js → yaml-BDujJdoL.js} +1 -1
- package/dist/shell/assets/yaml-BDujJdoL.js.br +0 -0
- package/dist/shell/assets/yaml-BDujJdoL.js.gz +0 -0
- package/dist/shell/index.html +2 -2
- package/package.json +1 -1
- package/dist/shell/assets/Editor-B-1lrUVF.js.br +0 -0
- package/dist/shell/assets/Editor-B-1lrUVF.js.gz +0 -0
- package/dist/shell/assets/XTerm-CZl-vWEO.js.br +0 -0
- package/dist/shell/assets/XTerm-CZl-vWEO.js.gz +0 -0
- package/dist/shell/assets/cssMode-BpigOakf.js.br +0 -0
- package/dist/shell/assets/cssMode-BpigOakf.js.gz +0 -0
- package/dist/shell/assets/freemarker2-vvh2KLdJ.js.br +0 -0
- package/dist/shell/assets/freemarker2-vvh2KLdJ.js.gz +0 -0
- package/dist/shell/assets/grammar-DBMSnNcB.js.gz +0 -0
- package/dist/shell/assets/handlebars-ExFAR1bz.js.br +0 -0
- package/dist/shell/assets/handlebars-ExFAR1bz.js.gz +0 -0
- package/dist/shell/assets/html-DpIWoe_L.js.br +0 -0
- package/dist/shell/assets/html-DpIWoe_L.js.gz +0 -0
- package/dist/shell/assets/htmlMode-vCEOKCM-.js.br +0 -0
- package/dist/shell/assets/htmlMode-vCEOKCM-.js.gz +0 -0
- package/dist/shell/assets/index-CHbNBHbD.css +0 -1
- package/dist/shell/assets/index-CHbNBHbD.css.br +0 -0
- package/dist/shell/assets/index-CHbNBHbD.css.gz +0 -0
- package/dist/shell/assets/index-D5GN5OOv.js +0 -276
- package/dist/shell/assets/index-D5GN5OOv.js.br +0 -0
- package/dist/shell/assets/index-D5GN5OOv.js.gz +0 -0
- package/dist/shell/assets/jsonMode-CZzpDv9U.js.br +0 -0
- package/dist/shell/assets/jsonMode-CZzpDv9U.js.gz +0 -0
- package/dist/shell/assets/liquid-5T0ZgJhW.js.br +0 -0
- package/dist/shell/assets/liquid-5T0ZgJhW.js.gz +0 -0
- package/dist/shell/assets/lspLanguageFeatures-DrkNrp6u.js.br +0 -0
- package/dist/shell/assets/lspLanguageFeatures-DrkNrp6u.js.gz +0 -0
- package/dist/shell/assets/mdx-DnLcCYKb.js.br +0 -0
- package/dist/shell/assets/mdx-DnLcCYKb.js.gz +0 -0
- package/dist/shell/assets/python-Di4WuyJe.js.br +0 -0
- package/dist/shell/assets/python-Di4WuyJe.js.gz +0 -0
- package/dist/shell/assets/razor-NpEB1IHs.js.br +0 -0
- package/dist/shell/assets/razor-NpEB1IHs.js.gz +0 -0
- package/dist/shell/assets/tsMode-BylSuxTF.js.br +0 -0
- package/dist/shell/assets/tsMode-BylSuxTF.js.gz +0 -0
- package/dist/shell/assets/xml-BYRl5HAh.js.br +0 -0
- package/dist/shell/assets/xml-BYRl5HAh.js.gz +0 -0
- package/dist/shell/assets/yaml-DeSD0TUR.js.br +0 -0
- package/dist/shell/assets/yaml-DeSD0TUR.js.gz +0 -0
package/dist/daemon.js
CHANGED
|
@@ -12,6 +12,7 @@ var TOOL_SEARCH = "ToolSearch";
|
|
|
12
12
|
var ASK_TOOL = "AskUserQuestion";
|
|
13
13
|
var TOYON_MCP_SERVER = "toyon";
|
|
14
14
|
var SPLIT_TOOL = "split";
|
|
15
|
+
var WORKTREES_TOOL = "worktrees";
|
|
15
16
|
function coalesce(entries) {
|
|
16
17
|
const out = [];
|
|
17
18
|
for (const entry of entries) {
|
|
@@ -45,6 +46,15 @@ function isWrittenKind(kind) {
|
|
|
45
46
|
function writingCall(call, input) {
|
|
46
47
|
return (isWrittenKind(call.kind) || call.name === ASK_TOOL || call.name === TOOL_SEARCH) && emptyInput(input);
|
|
47
48
|
}
|
|
49
|
+
var MCP_NAME = /^(?:mcp__(.+?)__(.+)|([A-Za-z0-9_-]+)\/([A-Za-z0-9_-]+))$/;
|
|
50
|
+
function mcpTool(name) {
|
|
51
|
+
const m = MCP_NAME.exec(name);
|
|
52
|
+
if (!m)
|
|
53
|
+
return null;
|
|
54
|
+
const server = m[1] ?? m[3] ?? "";
|
|
55
|
+
const tool = m[2] ?? m[4] ?? "";
|
|
56
|
+
return { server, tool: tool.replace(/[_-]+/g, " ").trim() };
|
|
57
|
+
}
|
|
48
58
|
function emptyInput(input) {
|
|
49
59
|
if (!input || typeof input !== "object")
|
|
50
60
|
return true;
|
|
@@ -117,6 +127,35 @@ function railOrder(rows) {
|
|
|
117
127
|
|
|
118
128
|
// ../shared/src/archiveRule.ts
|
|
119
129
|
var ARCHIVE_KEEP_RECENT = 5;
|
|
130
|
+
function heldBy(w, f) {
|
|
131
|
+
const c = f.counts(w.id);
|
|
132
|
+
if (c.dirty === undefined || c.ahead === undefined)
|
|
133
|
+
return "its changes are not counted yet";
|
|
134
|
+
if (c.dirty > 0)
|
|
135
|
+
return "it has uncommitted changes";
|
|
136
|
+
if (c.ahead > 0)
|
|
137
|
+
return "it has commits not on main";
|
|
138
|
+
if (f.drafted(w.id))
|
|
139
|
+
return "unsent text waits in its composer";
|
|
140
|
+
if (f.pending(w.id))
|
|
141
|
+
return "its agent is still busy";
|
|
142
|
+
const end = w.lastTurn?.end;
|
|
143
|
+
if (!end)
|
|
144
|
+
return "its agent has not run yet";
|
|
145
|
+
if (end === "stopped")
|
|
146
|
+
return "its last turn was stopped";
|
|
147
|
+
if (end === "failed")
|
|
148
|
+
return "its last turn failed";
|
|
149
|
+
if (end === "asking")
|
|
150
|
+
return "its agent is waiting on an answer";
|
|
151
|
+
if (isUnseen(w))
|
|
152
|
+
return "its last reply is unread";
|
|
153
|
+
if (w.planned)
|
|
154
|
+
return "it showed a plan";
|
|
155
|
+
if (w.pr?.state === "open")
|
|
156
|
+
return "its pull request is still open";
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
120
159
|
function archiveReason(wt, f) {
|
|
121
160
|
if (!nothingToKeep(wt, f))
|
|
122
161
|
return null;
|
|
@@ -129,16 +168,11 @@ var UNOPENED = "not opened in ";
|
|
|
129
168
|
function nothingToKeep(w, f) {
|
|
130
169
|
if (w.kind !== "worktree" || !hasOwnBranch(w))
|
|
131
170
|
return false;
|
|
132
|
-
if (
|
|
133
|
-
return false;
|
|
134
|
-
if (w.planned || w.pr?.state === "open")
|
|
135
|
-
return false;
|
|
136
|
-
if (f.viewed(w.id) || f.pending(w.id) || f.drafted(w.id))
|
|
171
|
+
if (f.viewed(w.id))
|
|
137
172
|
return false;
|
|
138
173
|
if (f.now - (w.viewedAt ?? w.createdAt) < f.afterMs)
|
|
139
174
|
return false;
|
|
140
|
-
|
|
141
|
-
return c.dirty === 0 && c.ahead === 0;
|
|
175
|
+
return heldBy(w, f) === null;
|
|
142
176
|
}
|
|
143
177
|
function rowsAhead(wt, rows) {
|
|
144
178
|
const tasks = railOrder(rows.filter((w) => w.kind === "worktree").map((worktree) => ({ worktree })));
|
|
@@ -6586,7 +6620,7 @@ ${text.slice(text.length - (CONTEXT_CHARS - HEAD_CHARS))}`;
|
|
|
6586
6620
|
}
|
|
6587
6621
|
|
|
6588
6622
|
// ../shared/src/protocol/limits.ts
|
|
6589
|
-
var PROTOCOL_VERSION =
|
|
6623
|
+
var PROTOCOL_VERSION = 133;
|
|
6590
6624
|
var PAGE_ERROR_MAX_CHARS = 2000;
|
|
6591
6625
|
var PAGE_TITLE_MAX_CHARS = 1000;
|
|
6592
6626
|
var FILE_MAX_CHARS = 5000000;
|
|
@@ -6948,6 +6982,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6948
6982
|
object({ t: literal("reveal"), worktreeId: id, path: relPath.optional() }),
|
|
6949
6983
|
object({ t: literal("reveal-path"), path: relPath }),
|
|
6950
6984
|
object({ t: literal("reveal-attachment"), worktreeId: id, file: string2().min(1).max(120) }),
|
|
6985
|
+
object({ t: literal("reveal-transcript"), worktreeId: id }),
|
|
6951
6986
|
object({ t: literal("stop-agent"), worktreeId: id }),
|
|
6952
6987
|
object({ t: literal("seen"), worktreeId: id }),
|
|
6953
6988
|
object({ t: literal("mark-unread"), worktreeId: id }),
|
|
@@ -8518,7 +8553,7 @@ async function turnRemoteOff(ts, daemonPort, file, was) {
|
|
|
8518
8553
|
// ../daemon/package.json
|
|
8519
8554
|
var package_default = {
|
|
8520
8555
|
name: "@toyon/daemon",
|
|
8521
|
-
version: "0.
|
|
8556
|
+
version: "0.20.0",
|
|
8522
8557
|
private: true,
|
|
8523
8558
|
type: "module",
|
|
8524
8559
|
main: "src/index.ts",
|
|
@@ -14372,7 +14407,13 @@ var DENIED_COMMANDS = [
|
|
|
14372
14407
|
"git branch -D",
|
|
14373
14408
|
"git branch -d",
|
|
14374
14409
|
"git branch --delete",
|
|
14375
|
-
"git worktree",
|
|
14410
|
+
"git worktree add",
|
|
14411
|
+
"git worktree remove",
|
|
14412
|
+
"git worktree move",
|
|
14413
|
+
"git worktree prune",
|
|
14414
|
+
"git worktree lock",
|
|
14415
|
+
"git worktree unlock",
|
|
14416
|
+
"git worktree repair",
|
|
14376
14417
|
"gh pr create",
|
|
14377
14418
|
"gh pr merge"
|
|
14378
14419
|
];
|
|
@@ -14422,6 +14463,17 @@ function opencodeEnv() {
|
|
|
14422
14463
|
};
|
|
14423
14464
|
}
|
|
14424
14465
|
|
|
14466
|
+
// ../daemon/src/agent/quick.ts
|
|
14467
|
+
function quickModelFor(spec, offered, current) {
|
|
14468
|
+
const quick = spec.quickModel;
|
|
14469
|
+
if (typeof quick === "function")
|
|
14470
|
+
return quick(offered, current);
|
|
14471
|
+
return quick && offered.includes(quick) ? quick : undefined;
|
|
14472
|
+
}
|
|
14473
|
+
function quickModelNamed(name) {
|
|
14474
|
+
return (offered) => offered.includes(name) ? name : offered.find((id) => id.includes(name));
|
|
14475
|
+
}
|
|
14476
|
+
|
|
14425
14477
|
// ../daemon/src/agent/sandbox.ts
|
|
14426
14478
|
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, renameSync, writeFileSync as writeFileSync4 } from "fs";
|
|
14427
14479
|
import { homedir as homedir2, tmpdir } from "os";
|
|
@@ -14754,7 +14806,7 @@ var BUILTIN_AGENTS = [
|
|
|
14754
14806
|
systemPrompt: "meta-append",
|
|
14755
14807
|
modes: { plan: "plan", build: "default" },
|
|
14756
14808
|
sideMeta: { claudeCode: { options: { tools: [], persistSession: false } } },
|
|
14757
|
-
quickModel: "haiku",
|
|
14809
|
+
quickModel: quickModelNamed("haiku"),
|
|
14758
14810
|
loginHint: "Claude is not logged in"
|
|
14759
14811
|
},
|
|
14760
14812
|
{
|
|
@@ -15571,6 +15623,65 @@ function unifiedDiff(oldText, newText, context = 3) {
|
|
|
15571
15623
|
`);
|
|
15572
15624
|
}
|
|
15573
15625
|
|
|
15626
|
+
// ../daemon/src/agent/acp/wakeup.ts
|
|
15627
|
+
var WAKEUP_TOOL = "ScheduleWakeup";
|
|
15628
|
+
var MIN_DELAY_S = 60;
|
|
15629
|
+
var MAX_DELAY_S = 3600;
|
|
15630
|
+
var GRACE_MS2 = 60000;
|
|
15631
|
+
function wakeupAt(name, input, now = Date.now()) {
|
|
15632
|
+
if (name !== WAKEUP_TOOL)
|
|
15633
|
+
return null;
|
|
15634
|
+
const rec = input && typeof input === "object" ? input : null;
|
|
15635
|
+
if (!rec || rec.stop === true)
|
|
15636
|
+
return null;
|
|
15637
|
+
const delay = rec.delaySeconds;
|
|
15638
|
+
if (typeof delay !== "number" || !Number.isFinite(delay))
|
|
15639
|
+
return null;
|
|
15640
|
+
return now + Math.min(MAX_DELAY_S, Math.max(MIN_DELAY_S, delay)) * 1000;
|
|
15641
|
+
}
|
|
15642
|
+
|
|
15643
|
+
class Wakeups {
|
|
15644
|
+
deps;
|
|
15645
|
+
held = new Map;
|
|
15646
|
+
constructor(deps) {
|
|
15647
|
+
this.deps = deps;
|
|
15648
|
+
}
|
|
15649
|
+
get size() {
|
|
15650
|
+
return this.held.size;
|
|
15651
|
+
}
|
|
15652
|
+
get next() {
|
|
15653
|
+
let next = null;
|
|
15654
|
+
for (const w of this.held.values())
|
|
15655
|
+
if (next === null || w.until < next)
|
|
15656
|
+
next = w.until;
|
|
15657
|
+
return next;
|
|
15658
|
+
}
|
|
15659
|
+
start(toolId, until) {
|
|
15660
|
+
const wait = Math.max(0, until - Date.now()) + (this.deps.graceMs ?? GRACE_MS2);
|
|
15661
|
+
const timer = setTimeout(() => this.finish(toolId, "no turn came; the row stops waiting", false), wait);
|
|
15662
|
+
timer.unref?.();
|
|
15663
|
+
this.held.set(toolId, { until, timer });
|
|
15664
|
+
this.deps.onChange?.();
|
|
15665
|
+
}
|
|
15666
|
+
wake(how) {
|
|
15667
|
+
for (const toolId of [...this.held.keys()])
|
|
15668
|
+
this.finish(toolId, how, false);
|
|
15669
|
+
}
|
|
15670
|
+
endAll(reason) {
|
|
15671
|
+
for (const toolId of [...this.held.keys()])
|
|
15672
|
+
this.finish(toolId, reason, true);
|
|
15673
|
+
}
|
|
15674
|
+
finish(toolId, output, isError) {
|
|
15675
|
+
const w = this.held.get(toolId);
|
|
15676
|
+
if (!w)
|
|
15677
|
+
return;
|
|
15678
|
+
clearTimeout(w.timer);
|
|
15679
|
+
this.held.delete(toolId);
|
|
15680
|
+
this.deps.note({ type: "tool-end", toolId, output, isError });
|
|
15681
|
+
this.deps.onChange?.();
|
|
15682
|
+
}
|
|
15683
|
+
}
|
|
15684
|
+
|
|
15574
15685
|
// ../daemon/src/agent/acp/map.ts
|
|
15575
15686
|
function spawnOf(meta) {
|
|
15576
15687
|
const claude = asRecord2(asRecord2(meta).claudeCode);
|
|
@@ -15598,7 +15709,10 @@ function toolSearchTitle(rawInput) {
|
|
|
15598
15709
|
const names = /^select:(.*)$/.exec(query.trim());
|
|
15599
15710
|
if (!names)
|
|
15600
15711
|
return `looked for a tool: ${query.trim()}`;
|
|
15601
|
-
const loaded = (names[1] ?? "").split(",").map((s) => s.trim()).filter(Boolean).
|
|
15712
|
+
const loaded = (names[1] ?? "").split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
|
|
15713
|
+
const mcp = mcpTool(s);
|
|
15714
|
+
return mcp ? `${mcp.server} ${mcp.tool}` : s;
|
|
15715
|
+
}).join(", ");
|
|
15602
15716
|
return loaded ? `loaded ${loaded}` : "";
|
|
15603
15717
|
}
|
|
15604
15718
|
var TOYON_TOOL = new RegExp(`^(?:mcp__${TOYON_MCP_SERVER}__|${TOYON_MCP_SERVER}/)([A-Za-z0-9_-]+)$`);
|
|
@@ -15676,7 +15790,7 @@ function abandoned(memos, parent, opening = false) {
|
|
|
15676
15790
|
}
|
|
15677
15791
|
return out;
|
|
15678
15792
|
}
|
|
15679
|
-
function mapUpdate(update, memos, tag, sink, background) {
|
|
15793
|
+
function mapUpdate(update, memos, tag, sink, background, wakeup) {
|
|
15680
15794
|
switch (update.sessionUpdate) {
|
|
15681
15795
|
case "agent_message_chunk": {
|
|
15682
15796
|
const { parentToolId } = spawnOf(update._meta);
|
|
@@ -15723,7 +15837,7 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
15723
15837
|
...spawn
|
|
15724
15838
|
});
|
|
15725
15839
|
if (update.status === "completed" || update.status === "failed")
|
|
15726
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
15840
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background, wakeup));
|
|
15727
15841
|
return out;
|
|
15728
15842
|
}
|
|
15729
15843
|
case "tool_call_update": {
|
|
@@ -15780,7 +15894,7 @@ function mapUpdate(update, memos, tag, sink, background) {
|
|
|
15780
15894
|
if (update.rawOutput !== undefined)
|
|
15781
15895
|
memo.rawOutput = update.rawOutput;
|
|
15782
15896
|
if ((update.status === "completed" || update.status === "failed") && !memo.ended) {
|
|
15783
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
15897
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background, wakeup));
|
|
15784
15898
|
}
|
|
15785
15899
|
return out;
|
|
15786
15900
|
}
|
|
@@ -15811,7 +15925,7 @@ function textOf(content, tag, what) {
|
|
|
15811
15925
|
log.debug(tag, `acp: dropping non-text ${what} block (${content.type})`);
|
|
15812
15926
|
return null;
|
|
15813
15927
|
}
|
|
15814
|
-
function endOf(toolId, memo, status, sink, background) {
|
|
15928
|
+
function endOf(toolId, memo, status, sink, background, wakeup) {
|
|
15815
15929
|
memo.ended = true;
|
|
15816
15930
|
const output = summarizeToolOutput(memo.content, memo.rawOutput);
|
|
15817
15931
|
if (status === "completed" && memo.kind === "execute" && background) {
|
|
@@ -15819,6 +15933,13 @@ function endOf(toolId, memo, status, sink, background) {
|
|
|
15819
15933
|
if (start && background(toolId, start, memo.input))
|
|
15820
15934
|
return { type: "tool-update", toolId, background: true };
|
|
15821
15935
|
}
|
|
15936
|
+
if (status === "completed" && wakeup) {
|
|
15937
|
+
const until = wakeupAt(memo.name || memo.title, memo.input);
|
|
15938
|
+
if (until !== null) {
|
|
15939
|
+
wakeup(toolId, until);
|
|
15940
|
+
return { type: "tool-update", toolId, background: true, until };
|
|
15941
|
+
}
|
|
15942
|
+
}
|
|
15822
15943
|
const images = sink ? toolImages(memo.content, sink) : [];
|
|
15823
15944
|
const detached = status === "completed" && !!memo.spawns && spawnDetached(wholeText(memo.content, memo.rawOutput));
|
|
15824
15945
|
return {
|
|
@@ -16012,10 +16133,10 @@ var SYSTEM_APPEND = [
|
|
|
16012
16133
|
"You are working inside a dedicated git worktree managed by Toyon.",
|
|
16013
16134
|
"Make every change inside the current working directory and never modify files outside it. Reading files outside it is fine when the user points you there.",
|
|
16014
16135
|
"When a change belongs in another project Toyon has open rather than in this worktree, do not edit outside it and do not stop at saying so: propose it with the `split` tool when you have one, as one piece with `where` set to `worktree` and `project` naming that project, its `plan` in markdown that project's agent builds from, with what is needed and why, how to reproduce it, and the paths here that depend on it; the user decides on a card, and once it is proposed you finish your turn.",
|
|
16015
|
-
"When the work you are planning splits into pieces that could go on in parallel, hand Toyon the list with the `split` tool when you have one, right before you ask for plan approval
|
|
16016
|
-
"The same tool, with one piece, is how you offer a separate thing you come across mid-task: a feature you would call its own, a follow-up you would otherwise park in a sentence, work the person asks to have done in a new worktree. Offer it once, as `worktree`, with a plan its agent builds from; a sentence saying it could be its own branch is not an offer, and a card the person can decline in one press is.",
|
|
16136
|
+
"When the work you are planning splits into pieces that could go on in parallel, hand Toyon the list with the `split` tool when you have one, right before you ask for plan approval, or, outside plan mode, before you start on your own pieces, never at the end of the turn: a sibling's agent then works while you do, and the user sees the card at once. For each piece, a title, a plan in markdown standing on its own, which its agent builds from as an approved plan, the attachments from this chat it needs by their labels (`Image 2`, `Paste 1`), and where it goes: `here` for what you will do yourself in this worktree (with `after` naming a branch or a piece of the list your own work must wait for, which Toyon then shows under the box until it lands), `worktree` for a new worktree of this project or, with `project`, of another open one (with `after` naming the branch it must wait for, another piece of the list by its title, or `this` for your own), or `send` for an open worktree already working on that part, named by its branch or title. The user reads the list beside your plan and may move any piece; Toyon acts on their answer, so never start a sibling's piece yourself, and do only the pieces marked `here`.",
|
|
16137
|
+
"The same tool, with one piece, is how you offer a separate thing you come across mid-task: a feature you would call its own, a follow-up you would otherwise park in a sentence, work the person asks to have done in a new worktree. Offer it once, as `worktree`, with a plan its agent builds from, as soon as you see it rather than when your own work is done; a sentence saying it could be its own branch is not an offer, and a card the person can decline in one press is.",
|
|
16017
16138
|
"A write Toyon refuses (outside the worktree, or to an agent's settings, hooks or plugin code such as .claude/settings.json, .claude/hooks or opencode.json) was refused by Toyon and not by the user, who was never asked. Report it as the boundary that stopped it, never as the user declining.",
|
|
16018
|
-
"Never run `git push`, delete branches, or create pull requests, and never offer to; shipping is handled by the Toyon UI.",
|
|
16139
|
+
"Never run `git push`, delete branches, or create pull requests, and never offer to; shipping is handled by the Toyon UI. Never add, remove, move or prune worktrees; Toyon owns them. Listing them with `git worktree list` is fine.",
|
|
16019
16140
|
"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.",
|
|
16020
16141
|
"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.",
|
|
16021
16142
|
"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.",
|
|
@@ -16023,6 +16144,7 @@ var SYSTEM_APPEND = [
|
|
|
16023
16144
|
"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.",
|
|
16024
16145
|
"`@some/path` in a message means read that file or directory first; `@changes` means this worktree's uncommitted files, which `git status` lists; `@<branch>` names another worktree of this project, and the context attached to the message says where its chat is on disk.",
|
|
16025
16146
|
"Toyon knows every worktree by a title and a branch; its directory, `wt-xxxx`, is an id that outlives both. When you name this worktree or another in prose, use its title in quotes or its branch, never the directory: the context attached to each message gives this worktree's title and branch, and a sibling's when the message names it.",
|
|
16147
|
+
"To find which worktree of this project worked on a topic, a ticket or a feature, or to see what is open and what was archived, call the `worktrees` tool when you have one: it lists them by title and branch with where each chat is on disk, and with a `query` it keeps only those whose title, branch or chat mention it. `git worktree list` knows neither titles nor chats, so it cannot answer that.",
|
|
16026
16148
|
'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.',
|
|
16027
16149
|
"Toyon runs those commands itself in every worktree, and the user watches the result live in a preview beside this chat, so never start a dev server or open a browser of your own to check your work. Each message says where this worktree's preview answers; fetch a page from there when you want to see it.",
|
|
16028
16150
|
"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.",
|
|
@@ -16166,7 +16288,7 @@ function piecesActedPrompt(pieces, outcomes) {
|
|
|
16166
16288
|
const started = outcomes.filter((o) => o.fate === "worktree" && !o.after && !o.error).map((o) => titled(o.pieceId));
|
|
16167
16289
|
const queued = outcomes.filter((o) => o.fate === "worktree" && o.after && !o.error);
|
|
16168
16290
|
const sent = outcomes.filter((o) => o.fate === "send" && !o.error);
|
|
16169
|
-
const here = outcomes.filter((o) => o.fate === "here").map((o) => titled(o.pieceId));
|
|
16291
|
+
const here = outcomes.filter((o) => o.fate === "here").map((o) => o.after ? `${titled(o.pieceId)} (once ${o.after} lands)` : titled(o.pieceId));
|
|
16170
16292
|
const skipped = outcomes.filter((o) => o.fate === "skip").map((o) => titled(o.pieceId));
|
|
16171
16293
|
const failed = outcomes.filter((o) => o.error).map((o) => `${titled(o.pieceId)} (${o.error})`);
|
|
16172
16294
|
const parts = [];
|
|
@@ -16203,8 +16325,11 @@ function pieceAskPrompt(text, project) {
|
|
|
16203
16325
|
const where = project ? `in a new worktree of ${project.name} (${project.path}), another project open in Toyon. Propose it with the \`split\` tool: one piece, \`where\` set to \`worktree\` and \`project\` to "${project.path}", with a title and a plan that project's agent builds from, with what is needed and why, how to reproduce it, and the paths here that depend on it.` : "in a new worktree of this project. Propose it with the `split` tool: one piece, `where` set to `worktree`, with a title and a plan its agent builds from, with what is needed and why and the paths here that depend on it.";
|
|
16204
16326
|
return `The user wants part of this work done ${where}` + (own ? ` The user's own words for what it should do: ${own}` : "") + " The user decides on the card; do not start anything else for it.";
|
|
16205
16327
|
}
|
|
16206
|
-
function pieceLandedContext(title, project, url, same, again) {
|
|
16328
|
+
function pieceLandedContext(title, project, url, same, again, awaited) {
|
|
16207
16329
|
const where = url ? ` (${url})` : "";
|
|
16330
|
+
if (awaited) {
|
|
16331
|
+
return `The branch your own piece waited for, "${title}", has landed on this project's main${where}; that piece can go ahead now, and Toyon rebases onto main when this lands.`;
|
|
16332
|
+
}
|
|
16208
16333
|
if (again) {
|
|
16209
16334
|
return `The branch this worktree waited for before it started, "${title}", landed again on this project's main${where} with follow-up commits; Toyon rebases onto main when this lands, and its latest commits are readable now if they touch your piece.`;
|
|
16210
16335
|
}
|
|
@@ -16227,6 +16352,10 @@ function archivedFate(a) {
|
|
|
16227
16352
|
return "without landing; its commits are kept under an archive ref, not on any checkout";
|
|
16228
16353
|
return "without landing, and git kept nothing of it";
|
|
16229
16354
|
}
|
|
16355
|
+
function siblingParagraph(s) {
|
|
16356
|
+
const chat = `Its chat is at ${s.transcript}, one JSON object per line: a "user-message" event is what the person wrote, the "text-delta" events after it are the agent's reply, and the rest is tool activity.`;
|
|
16357
|
+
return s.archived ? `@${s.branch} was the worktree "${s.title}" in this project, archived on ${dayOf(s.archived.at)} ${archivedFate(s.archived)}. Its checkout at ${s.path} is gone. ${chat}` : `@${s.branch} is the worktree "${s.title}" in this project, checked out at ${s.path}. ${chat}`;
|
|
16358
|
+
}
|
|
16230
16359
|
function worktreeContext(text, siblings) {
|
|
16231
16360
|
const out = [];
|
|
16232
16361
|
const said = new Set;
|
|
@@ -16237,8 +16366,7 @@ function worktreeContext(text, siblings) {
|
|
|
16237
16366
|
if (!named.test(text))
|
|
16238
16367
|
continue;
|
|
16239
16368
|
said.add(s.branch);
|
|
16240
|
-
|
|
16241
|
-
out.push(s.archived ? `@${s.branch} was the worktree "${s.title}" in this project, archived on ${dayOf(s.archived.at)} ${archivedFate(s.archived)}. Its checkout at ${s.path} is gone. ${chat}` : `@${s.branch} is the worktree "${s.title}" in this project, checked out at ${s.path}. ${chat}`);
|
|
16369
|
+
out.push(siblingParagraph(s));
|
|
16242
16370
|
}
|
|
16243
16371
|
return out;
|
|
16244
16372
|
}
|
|
@@ -16478,14 +16606,6 @@ class Transcript {
|
|
|
16478
16606
|
}
|
|
16479
16607
|
}
|
|
16480
16608
|
|
|
16481
|
-
// ../daemon/src/agent/quick.ts
|
|
16482
|
-
function quickModelFor(spec, offered, current) {
|
|
16483
|
-
const quick = spec.quickModel;
|
|
16484
|
-
if (typeof quick === "function")
|
|
16485
|
-
return quick(offered, current);
|
|
16486
|
-
return quick && offered.includes(quick) ? quick : undefined;
|
|
16487
|
-
}
|
|
16488
|
-
|
|
16489
16609
|
// ../daemon/src/agent/acp/ask.ts
|
|
16490
16610
|
async function askOnce(ctx, a) {
|
|
16491
16611
|
const meta = a.spec.systemPrompt === "meta-append";
|
|
@@ -16761,6 +16881,7 @@ class AcpSession {
|
|
|
16761
16881
|
reaper = null;
|
|
16762
16882
|
own = null;
|
|
16763
16883
|
tasks;
|
|
16884
|
+
wakeups;
|
|
16764
16885
|
asking = 0;
|
|
16765
16886
|
asks = new Map;
|
|
16766
16887
|
openCards = new Set;
|
|
@@ -16776,10 +16897,15 @@ class AcpSession {
|
|
|
16776
16897
|
this.log = new Transcript(transcriptPathFor(d.transcriptsDir, d.worktreeId), d.worktreeId);
|
|
16777
16898
|
this.tasks = new BackgroundTasks({
|
|
16778
16899
|
note: (e) => this.emit(e),
|
|
16779
|
-
onEnd: () => this.
|
|
16900
|
+
onEnd: () => this.holdingChanged(),
|
|
16780
16901
|
...d.taskPollMs !== undefined ? { pollMs: d.taskPollMs } : {},
|
|
16781
16902
|
...d.claudeConfigDir !== undefined ? { configDir: d.claudeConfigDir } : {}
|
|
16782
16903
|
});
|
|
16904
|
+
this.wakeups = new Wakeups({
|
|
16905
|
+
note: (e) => this.emit(e),
|
|
16906
|
+
onChange: () => this.holdingChanged(),
|
|
16907
|
+
...d.wakeupGraceMs !== undefined ? { graceMs: d.wakeupGraceMs } : {}
|
|
16908
|
+
});
|
|
16783
16909
|
this.seq = nextNumbers(this.log.entries.map(({ event }) => event.type === "user-message" || event.type === "handed" ? event.attachments : undefined));
|
|
16784
16910
|
const seal = sealOpenTurn(this.log.entries);
|
|
16785
16911
|
const open = new Set;
|
|
@@ -16863,8 +16989,7 @@ class AcpSession {
|
|
|
16863
16989
|
const pieces = this.adopted.pieces;
|
|
16864
16990
|
this.adopted = null;
|
|
16865
16991
|
if (event.outcome !== "answered") {
|
|
16866
|
-
|
|
16867
|
-
if (list?.type === "pieces-proposed" && list.by === "toyon") {
|
|
16992
|
+
if (this.proposed(pieces)?.by === "toyon") {
|
|
16868
16993
|
this.note({
|
|
16869
16994
|
type: "pieces-declined",
|
|
16870
16995
|
id: pieces,
|
|
@@ -16892,9 +17017,7 @@ class AcpSession {
|
|
|
16892
17017
|
return;
|
|
16893
17018
|
this.heldPieces = null;
|
|
16894
17019
|
this.openCards.add(id);
|
|
16895
|
-
const
|
|
16896
|
-
const pieces = proposed?.type === "pieces-proposed" ? proposed.pieces : [];
|
|
16897
|
-
const plan = this.d.onPiecesShown?.(id, pieces);
|
|
17020
|
+
const plan = this.d.onPiecesShown?.(id, this.proposed(id)?.pieces ?? []);
|
|
16898
17021
|
this.emit({ type: "pieces-shown", id, ...typeof plan === "string" ? { plan } : {}, ts: Date.now() });
|
|
16899
17022
|
}
|
|
16900
17023
|
get commands() {
|
|
@@ -17003,6 +17126,16 @@ class AcpSession {
|
|
|
17003
17126
|
get stopping() {
|
|
17004
17127
|
return this.turn?.stopping ?? false;
|
|
17005
17128
|
}
|
|
17129
|
+
get holding() {
|
|
17130
|
+
return this.tasks.size + this.wakeups.size;
|
|
17131
|
+
}
|
|
17132
|
+
get wakes() {
|
|
17133
|
+
return this.wakeups.next;
|
|
17134
|
+
}
|
|
17135
|
+
holdingChanged() {
|
|
17136
|
+
this.d.onHolding?.();
|
|
17137
|
+
this.maybeArmReaper();
|
|
17138
|
+
}
|
|
17006
17139
|
setStopping(on) {
|
|
17007
17140
|
const turn = this.turn;
|
|
17008
17141
|
if (!turn || turn.stopping === on)
|
|
@@ -17150,6 +17283,7 @@ class AcpSession {
|
|
|
17150
17283
|
}
|
|
17151
17284
|
async drain() {
|
|
17152
17285
|
this.endOwn("end_turn");
|
|
17286
|
+
this.wakeups.wake("a message came first");
|
|
17153
17287
|
this.turn = { open: false, stopping: false };
|
|
17154
17288
|
this.clearReaper();
|
|
17155
17289
|
this.setStatus("working");
|
|
@@ -17406,8 +17540,9 @@ class AcpSession {
|
|
|
17406
17540
|
const e = entries[i].event;
|
|
17407
17541
|
if (isMessage(e))
|
|
17408
17542
|
break;
|
|
17409
|
-
if (e.type === "piece-landed")
|
|
17410
|
-
out.unshift(pieceLandedContext(e.title, e.repoName, e.url, e.same, e.again));
|
|
17543
|
+
if (e.type === "piece-landed") {
|
|
17544
|
+
out.unshift(pieceLandedContext(e.title, e.repoName, e.url, e.same, e.again, e.awaited));
|
|
17545
|
+
}
|
|
17411
17546
|
}
|
|
17412
17547
|
return out;
|
|
17413
17548
|
}
|
|
@@ -17445,6 +17580,7 @@ class AcpSession {
|
|
|
17445
17580
|
this.d.mcp?.close();
|
|
17446
17581
|
this.cancelAsks();
|
|
17447
17582
|
this.tasks.endAll("the agent stopped, and the command with it");
|
|
17583
|
+
this.wakeups.endAll("the agent stopped before it woke");
|
|
17448
17584
|
this.endOwn("interrupted");
|
|
17449
17585
|
}
|
|
17450
17586
|
});
|
|
@@ -17647,7 +17783,12 @@ class AcpSession {
|
|
|
17647
17783
|
this.d.onOptionChanged?.(category, opt.current);
|
|
17648
17784
|
}
|
|
17649
17785
|
}
|
|
17650
|
-
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) =>
|
|
17786
|
+
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) => {
|
|
17787
|
+
const taken = this.tasks.start(toolId, start, input);
|
|
17788
|
+
if (taken)
|
|
17789
|
+
this.holdingChanged();
|
|
17790
|
+
return taken;
|
|
17791
|
+
}, (toolId, until) => this.wakeups.start(toolId, until));
|
|
17651
17792
|
this.forward(live, events);
|
|
17652
17793
|
this.holdInput(live, params.update);
|
|
17653
17794
|
}
|
|
@@ -17750,9 +17891,23 @@ class AcpSession {
|
|
|
17750
17891
|
}
|
|
17751
17892
|
if (plan && approved && planPath)
|
|
17752
17893
|
return this.sayPlanEdited(detail, planPath).then(() => res);
|
|
17894
|
+
if (plan && !approved && decided && this.proposed(decided)?.by !== "toyon") {
|
|
17895
|
+
return this.sayPiecesDeclined().then(() => res);
|
|
17896
|
+
}
|
|
17753
17897
|
return res;
|
|
17754
17898
|
});
|
|
17755
17899
|
}
|
|
17900
|
+
proposed(id) {
|
|
17901
|
+
const list = this.log.entries.findLast(({ event }) => event.type === "pieces-proposed" && event.id === id)?.event;
|
|
17902
|
+
return list?.type === "pieces-proposed" ? list : undefined;
|
|
17903
|
+
}
|
|
17904
|
+
async sayPiecesDeclined() {
|
|
17905
|
+
try {
|
|
17906
|
+
await this.deliver("The pieces list you handed over went back with the plan. Hand over a list again with `split` before you ask for approval.");
|
|
17907
|
+
} catch (e) {
|
|
17908
|
+
log.warn(this.d.worktreeId, "the declined list was not sent", e);
|
|
17909
|
+
}
|
|
17910
|
+
}
|
|
17756
17911
|
async sayPlanEdited(proposed, path) {
|
|
17757
17912
|
try {
|
|
17758
17913
|
if (!await this.d.planEdited?.(path, proposed))
|
|
@@ -17845,6 +18000,7 @@ class AcpSession {
|
|
|
17845
18000
|
return;
|
|
17846
18001
|
this.own = { open: new Set, settle: null };
|
|
17847
18002
|
this.clearReaper();
|
|
18003
|
+
this.wakeups.wake("woke");
|
|
17848
18004
|
this.emit({ type: "turn-start", ts: Date.now() });
|
|
17849
18005
|
this.syncStatus();
|
|
17850
18006
|
}
|
|
@@ -17874,7 +18030,7 @@ class AcpSession {
|
|
|
17874
18030
|
this.maybeArmReaper();
|
|
17875
18031
|
}
|
|
17876
18032
|
quiet() {
|
|
17877
|
-
return !this.running && !this.own && !this.asking && this.asks.size === 0 && this.
|
|
18033
|
+
return !this.running && !this.own && !this.asking && this.asks.size === 0 && this.holding === 0;
|
|
17878
18034
|
}
|
|
17879
18035
|
maybeArmReaper() {
|
|
17880
18036
|
if (this.quiet() && this.conn && !this.stopped)
|
|
@@ -17900,6 +18056,7 @@ class AcpSession {
|
|
|
17900
18056
|
async dropConn(why) {
|
|
17901
18057
|
this.cancelAsks();
|
|
17902
18058
|
this.tasks.endAll("the agent stopped, and the command with it");
|
|
18059
|
+
this.wakeups.endAll("the agent stopped before it woke");
|
|
17903
18060
|
const conn = this.conn;
|
|
17904
18061
|
this.conn = null;
|
|
17905
18062
|
this.live = null;
|
|
@@ -19221,6 +19378,7 @@ function defaultAgent(wt, d, preview, onProcess) {
|
|
|
19221
19378
|
onEvent: (event, seq) => d.hub.emit("agent", wt.id, seq, event),
|
|
19222
19379
|
onStatus: (status) => d.hub.emit("agentStatus", wt.id, status),
|
|
19223
19380
|
onStopping: () => d.hub.emit("agentStopping", wt.id),
|
|
19381
|
+
onHolding: () => d.hub.emit("agentHolding", wt.id),
|
|
19224
19382
|
beforeTurn: async () => d.turnStarting?.(wt.id),
|
|
19225
19383
|
onAuth: (agentId, o) => d.readiness?.observe(agentId, o),
|
|
19226
19384
|
onLimits: (agentId, limits) => d.readiness?.observeLimits(agentId, limits),
|
|
@@ -21019,8 +21177,13 @@ function humanMs(ms) {
|
|
|
21019
21177
|
|
|
21020
21178
|
// ../daemon/src/core/awake.ts
|
|
21021
21179
|
var WAIT_MS = 30 * 60000;
|
|
21180
|
+
var BRIDGE_MS = 60000;
|
|
21181
|
+
var CARRIED_ENV = "TOYON_KEEP_AWAKE_CARRIED";
|
|
21182
|
+
function hasIdleSleep(raw, platform) {
|
|
21183
|
+
return raw !== "off" && platform === "darwin";
|
|
21184
|
+
}
|
|
21022
21185
|
function idleSleepAssertion(raw, platform = process.platform) {
|
|
21023
|
-
if (raw
|
|
21186
|
+
if (!hasIdleSleep(raw, platform))
|
|
21024
21187
|
return null;
|
|
21025
21188
|
return () => {
|
|
21026
21189
|
const child = spawn3("/usr/bin/caffeinate", ["-i", "-w", String(process.pid)], { stdio: "ignore" });
|
|
@@ -21031,6 +21194,22 @@ function idleSleepAssertion(raw, platform = process.platform) {
|
|
|
21031
21194
|
};
|
|
21032
21195
|
};
|
|
21033
21196
|
}
|
|
21197
|
+
function idleSleepBridge(raw, platform = process.platform) {
|
|
21198
|
+
if (!hasIdleSleep(raw, platform))
|
|
21199
|
+
return null;
|
|
21200
|
+
return (ms) => {
|
|
21201
|
+
const child = spawn3("/usr/bin/caffeinate", ["-i", "-t", String(Math.ceil(ms / 1000))], {
|
|
21202
|
+
stdio: "ignore",
|
|
21203
|
+
detached: true
|
|
21204
|
+
});
|
|
21205
|
+
child.on("error", (e) => log.warn("awake", "caffeinate did not start, so the Mac may sleep during the restart", e));
|
|
21206
|
+
child.unref();
|
|
21207
|
+
};
|
|
21208
|
+
}
|
|
21209
|
+
function carryEnv(env, held) {
|
|
21210
|
+
const { [CARRIED_ENV]: _stale, ...rest } = env;
|
|
21211
|
+
return held ? { ...rest, [CARRIED_ENV]: "1" } : rest;
|
|
21212
|
+
}
|
|
21034
21213
|
|
|
21035
21214
|
class KeepAwake {
|
|
21036
21215
|
d;
|
|
@@ -21046,6 +21225,10 @@ class KeepAwake {
|
|
|
21046
21225
|
this.d = d;
|
|
21047
21226
|
this.waitMs = d.waitMs ?? WAIT_MS;
|
|
21048
21227
|
this.now = d.now ?? Date.now;
|
|
21228
|
+
if (d.carried && d.assert) {
|
|
21229
|
+
this.remoteLeftAt = this.now();
|
|
21230
|
+
log.info("awake", `the last daemon was holding off idle sleep; holding on for ${humanMs(this.waitMs)} unless a device comes back`);
|
|
21231
|
+
}
|
|
21049
21232
|
d.hub.on("agentStatus", (_id, status) => {
|
|
21050
21233
|
if (status === "waiting")
|
|
21051
21234
|
this.waitingAt = this.now();
|
|
@@ -21067,10 +21250,14 @@ class KeepAwake {
|
|
|
21067
21250
|
this.remote = n;
|
|
21068
21251
|
this.reconsider();
|
|
21069
21252
|
}
|
|
21070
|
-
stop() {
|
|
21253
|
+
stop(opts = {}) {
|
|
21254
|
+
const held = this.release !== null;
|
|
21071
21255
|
this.gone = true;
|
|
21072
21256
|
this.disarm();
|
|
21257
|
+
if (held && opts.handoff)
|
|
21258
|
+
this.d.bridge?.(BRIDGE_MS);
|
|
21073
21259
|
this.hold(false);
|
|
21260
|
+
return held;
|
|
21074
21261
|
}
|
|
21075
21262
|
reconsider() {
|
|
21076
21263
|
if (this.gone || !this.d.assert)
|
|
@@ -21655,12 +21842,12 @@ async function replacementRuns() {
|
|
|
21655
21842
|
reason: `the bun Toyon runs on did not start (${process.execPath}: ${why}); an install may still be writing it`
|
|
21656
21843
|
};
|
|
21657
21844
|
}
|
|
21658
|
-
function respawn(logFile) {
|
|
21845
|
+
function respawn(logFile, env = process.env) {
|
|
21659
21846
|
const logFd = openLog(logFile);
|
|
21660
21847
|
const child = spawn4(process.execPath, process.argv.slice(1), {
|
|
21661
21848
|
detached: true,
|
|
21662
21849
|
stdio: ["ignore", logFd, logFd],
|
|
21663
|
-
env: { ...
|
|
21850
|
+
env: { ...env }
|
|
21664
21851
|
});
|
|
21665
21852
|
child.on("error", (e) => log.warn("daemon", `the replacement did not start: ${e.message}`));
|
|
21666
21853
|
child.unref();
|
|
@@ -29809,6 +29996,9 @@ var handlers = {
|
|
|
29809
29996
|
throw new UserError("that attachment is no longer on this machine");
|
|
29810
29997
|
s.files.revealPath(path);
|
|
29811
29998
|
},
|
|
29999
|
+
"reveal-transcript"(msg, _ctx, s) {
|
|
30000
|
+
s.files.revealPath(s.worktrees.transcriptPath(msg.worktreeId));
|
|
30001
|
+
},
|
|
29812
30002
|
async "discard-files"(msg, _ctx, s) {
|
|
29813
30003
|
try {
|
|
29814
30004
|
for (const path of msg.paths)
|
|
@@ -30572,6 +30762,51 @@ function createFetch(opts) {
|
|
|
30572
30762
|
};
|
|
30573
30763
|
}
|
|
30574
30764
|
|
|
30765
|
+
// ../daemon/src/server/rows.ts
|
|
30766
|
+
var rowsStampOf = (rowsJson, trunksJson) => Bun.hash(`{"rows":[${rowsJson.join(",")}],"trunks":${trunksJson}}`).toString(16);
|
|
30767
|
+
|
|
30768
|
+
class Published {
|
|
30769
|
+
byId = new Map;
|
|
30770
|
+
ids = [];
|
|
30771
|
+
trunks = {};
|
|
30772
|
+
trunksJson = "{}";
|
|
30773
|
+
stampNow = "";
|
|
30774
|
+
current() {
|
|
30775
|
+
return { rows: this.ids.map((id) => this.byId.get(id).row), trunks: this.trunks, stamp: this.stampNow };
|
|
30776
|
+
}
|
|
30777
|
+
publish(next) {
|
|
30778
|
+
const seen = new Map;
|
|
30779
|
+
const ids = [];
|
|
30780
|
+
const jsons = [];
|
|
30781
|
+
const changed = [];
|
|
30782
|
+
for (const row of next.rows) {
|
|
30783
|
+
const json = JSON.stringify(row);
|
|
30784
|
+
ids.push(row.id);
|
|
30785
|
+
jsons.push(json);
|
|
30786
|
+
if (this.byId.get(row.id)?.json !== json)
|
|
30787
|
+
changed.push(row);
|
|
30788
|
+
seen.set(row.id, { row, json });
|
|
30789
|
+
}
|
|
30790
|
+
const trunksJson = JSON.stringify(next.trunks);
|
|
30791
|
+
const trunksMoved = trunksJson !== this.trunksJson;
|
|
30792
|
+
const orderMoved = ids.length !== this.ids.length || ids.some((id, i) => id !== this.ids[i]);
|
|
30793
|
+
this.byId = seen;
|
|
30794
|
+
this.ids = ids;
|
|
30795
|
+
this.trunks = next.trunks;
|
|
30796
|
+
this.trunksJson = trunksJson;
|
|
30797
|
+
this.stampNow = rowsStampOf(jsons, trunksJson);
|
|
30798
|
+
if (changed.length === 0 && !trunksMoved && !orderMoved)
|
|
30799
|
+
return null;
|
|
30800
|
+
return {
|
|
30801
|
+
t: "worktrees",
|
|
30802
|
+
ids,
|
|
30803
|
+
rows: changed,
|
|
30804
|
+
...trunksMoved ? { trunks: next.trunks } : {},
|
|
30805
|
+
stamp: this.stampNow
|
|
30806
|
+
};
|
|
30807
|
+
}
|
|
30808
|
+
}
|
|
30809
|
+
|
|
30575
30810
|
// ../daemon/src/server/stamps.ts
|
|
30576
30811
|
var stampOf = (part) => Bun.hash(JSON.stringify(part)).toString(16);
|
|
30577
30812
|
var visitsStamp = (visits) => stampOf(Object.entries(visits).map(([repoId, list]) => [repoId, list.map((e) => [e.path, e.title ?? ""])]));
|
|
@@ -30725,6 +30960,22 @@ function startServer(opts) {
|
|
|
30725
30960
|
agents: s.agents.adapters(),
|
|
30726
30961
|
sockets: [...sockets].map((ws) => ({ subs: [...ws.data.subs], sent: ws.data.sent, bytes: ws.data.bytes }))
|
|
30727
30962
|
});
|
|
30963
|
+
const published = new Published;
|
|
30964
|
+
const sendRows = (frame) => {
|
|
30965
|
+
const json = JSON.stringify(frame);
|
|
30966
|
+
for (const ws of sockets)
|
|
30967
|
+
if (ws.data.helloed)
|
|
30968
|
+
raw(ws, json);
|
|
30969
|
+
};
|
|
30970
|
+
const publishRows = () => {
|
|
30971
|
+
try {
|
|
30972
|
+
const frame = published.publish(s.worktrees.snapshot());
|
|
30973
|
+
if (frame)
|
|
30974
|
+
sendRows(frame);
|
|
30975
|
+
} catch (e) {
|
|
30976
|
+
log.warn("ws", "worktrees broadcast failed", e);
|
|
30977
|
+
}
|
|
30978
|
+
};
|
|
30728
30979
|
let statusesQueued = false;
|
|
30729
30980
|
const worktreesChanged = () => {
|
|
30730
30981
|
if (statusesQueued)
|
|
@@ -30732,20 +30983,16 @@ function startServer(opts) {
|
|
|
30732
30983
|
statusesQueued = true;
|
|
30733
30984
|
setTimeout(() => {
|
|
30734
30985
|
statusesQueued = false;
|
|
30735
|
-
|
|
30736
|
-
const { rows, trunks } = s.worktrees.snapshot();
|
|
30737
|
-
broadcast({ t: "worktrees", rows, trunks, stamp: stampOf({ rows, trunks }) });
|
|
30738
|
-
} catch (e) {
|
|
30739
|
-
log.warn("ws", "worktrees broadcast failed", e);
|
|
30740
|
-
}
|
|
30986
|
+
publishRows();
|
|
30741
30987
|
}, 0);
|
|
30742
30988
|
};
|
|
30743
30989
|
s.hub.on("worktreesChanged", worktreesChanged);
|
|
30744
30990
|
s.hub.on("agentStatus", worktreesChanged);
|
|
30745
30991
|
s.hub.on("agentStopping", worktreesChanged);
|
|
30992
|
+
s.hub.on("agentHolding", worktreesChanged);
|
|
30993
|
+
s.hub.on("proc", worktreesChanged);
|
|
30746
30994
|
s.hub.on("reposChanged", () => broadcast({ t: "repos", repos: s.state.repos, stamp: stampOf(s.state.repos) }));
|
|
30747
30995
|
s.hub.on("pendingChanged", () => broadcast({ t: "pending-repos", pending: s.repos.pending }));
|
|
30748
|
-
s.hub.on("proc", (worktreeId, proc) => broadcast({ t: "proc", worktreeId, proc }));
|
|
30749
30996
|
s.hub.on("log", (worktreeId, proc, line, retract) => sendTo(worktreeId, retract ? { t: "log", worktreeId, proc, line, retract } : { t: "log", worktreeId, proc, line }));
|
|
30750
30997
|
s.hub.on("agent", (worktreeId, seq, event) => sendTo(worktreeId, { t: "agent", worktreeId, seq, event }));
|
|
30751
30998
|
s.hub.on("queue", (worktreeId, items) => sendTo(worktreeId, { t: "queue", worktreeId, items }));
|
|
@@ -30814,15 +31061,19 @@ function startServer(opts) {
|
|
|
30814
31061
|
s.hub.on("archiveChanged", (repoId) => broadcast({ t: "archived", repoId, items: s.worktrees.archived(repoId) }));
|
|
30815
31062
|
s.hub.on("draftChanged", (boxId, text, clientId) => broadcast({ t: "draft", boxId, text, ...clientId ? { clientId } : {} }));
|
|
30816
31063
|
s.hub.on("attachmentsChanged", (boxId, items, clientId) => broadcast({ t: "attachments", boxId, items, ...clientId ? { clientId } : {} }));
|
|
30817
|
-
const
|
|
31064
|
+
const helloReads = async () => {
|
|
30818
31065
|
await s.update.refresh();
|
|
30819
|
-
|
|
31066
|
+
return { registry: await s.update.registry(), gitIdentity: await s.repos.gitIdentity() };
|
|
31067
|
+
};
|
|
31068
|
+
const helloNow = (read, known) => {
|
|
31069
|
+
publishRows();
|
|
31070
|
+
const { rows, trunks } = published.current();
|
|
30820
31071
|
const parts = { rows, trunks, repos: s.state.repos, visits: s.routes.historyAll(), themes: s.themes.themes };
|
|
30821
31072
|
const full = {
|
|
30822
31073
|
t: "hello",
|
|
30823
31074
|
version,
|
|
30824
31075
|
install: s.update.install(),
|
|
30825
|
-
registry:
|
|
31076
|
+
registry: read.registry,
|
|
30826
31077
|
protocol: PROTOCOL_VERSION,
|
|
30827
31078
|
...parts,
|
|
30828
31079
|
stamps: stampsOf(parts),
|
|
@@ -30839,7 +31090,7 @@ function startServer(opts) {
|
|
|
30839
31090
|
machines: s.state.machines,
|
|
30840
31091
|
letIn: s.state.letIn,
|
|
30841
31092
|
knocks: opts.knocks.pending(),
|
|
30842
|
-
gitIdentity:
|
|
31093
|
+
gitIdentity: read.gitIdentity,
|
|
30843
31094
|
pending: s.repos.pending,
|
|
30844
31095
|
self: s.self.get(),
|
|
30845
31096
|
update: s.update.get(),
|
|
@@ -30887,7 +31138,7 @@ function startServer(opts) {
|
|
|
30887
31138
|
}),
|
|
30888
31139
|
managed: { source: opts.managed.source, hash: opts.managed.hash },
|
|
30889
31140
|
preview: (id) => s.runtime.get(id)?.proxy?.handler ?? null,
|
|
30890
|
-
bootstrap:
|
|
31141
|
+
bootstrap: async () => helloNow(await helloReads(), null),
|
|
30891
31142
|
manifestGround: () => s.themes.current().colors.surface1,
|
|
30892
31143
|
open: (path) => s.opens.open(path),
|
|
30893
31144
|
restart: (now) => s.restarter.request({ now }),
|
|
@@ -30912,7 +31163,9 @@ function startServer(opts) {
|
|
|
30912
31163
|
}
|
|
30913
31164
|
sockets.add(ws);
|
|
30914
31165
|
link(ws);
|
|
30915
|
-
|
|
31166
|
+
const read = await helloReads();
|
|
31167
|
+
send(ws, helloNow(read, ws.data.known ?? null));
|
|
31168
|
+
ws.data.helloed = true;
|
|
30916
31169
|
fireAndForget("agents", s.agents.retryFailed(), "agent install retry");
|
|
30917
31170
|
for (const o of s.opens.takePending())
|
|
30918
31171
|
send(ws, openedFrame(o));
|
|
@@ -32496,6 +32749,146 @@ async function recentSubjects(path, defaultBr) {
|
|
|
32496
32749
|
`).filter(Boolean) : [];
|
|
32497
32750
|
}
|
|
32498
32751
|
|
|
32752
|
+
// ../daemon/src/worktrees/lookup.ts
|
|
32753
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
32754
|
+
var ARCHIVED_MAX = 20;
|
|
32755
|
+
var ASKED_MAX = 200;
|
|
32756
|
+
|
|
32757
|
+
class WorktreeLookup {
|
|
32758
|
+
d;
|
|
32759
|
+
constructor(d) {
|
|
32760
|
+
this.d = d;
|
|
32761
|
+
}
|
|
32762
|
+
tool(worktreeId) {
|
|
32763
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
32764
|
+
const open = wt ? this.siblings(wt).length : 0;
|
|
32765
|
+
const gone = wt ? this.d.archived(wt.repoId).length : 0;
|
|
32766
|
+
return {
|
|
32767
|
+
name: WORKTREES_TOOL,
|
|
32768
|
+
description: `List the other worktrees of this project, open and archived, by title and branch, with where each chat is on disk and what it was about. Call it to find which worktree worked on a topic, a ticket or a feature, or to see what else is open. Right now ${open} other ${open === 1 ? "worktree is" : "worktrees are"} open and ${gone} ${gone === 1 ? "is" : "are"} archived. Without a query the open ones are listed and the newest ${ARCHIVED_MAX} archived; with one, only the worktrees whose title, branch or chat mention every word of it.`,
|
|
32769
|
+
inputSchema: {
|
|
32770
|
+
type: "object",
|
|
32771
|
+
properties: {
|
|
32772
|
+
query: {
|
|
32773
|
+
type: "string",
|
|
32774
|
+
description: "Words to look for, such as a ticket key, a product name or a feature: a worktree is listed when its title, branch or what the person wrote in its chat contains every word, in any case. Leave it out to list everything."
|
|
32775
|
+
}
|
|
32776
|
+
}
|
|
32777
|
+
}
|
|
32778
|
+
};
|
|
32779
|
+
}
|
|
32780
|
+
async call(worktreeId, args) {
|
|
32781
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
32782
|
+
if (!wt)
|
|
32783
|
+
return { text: "unknown worktree", isError: true };
|
|
32784
|
+
const raw = args && typeof args === "object" ? args.query : undefined;
|
|
32785
|
+
const query = typeof raw === "string" ? raw.trim() : "";
|
|
32786
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
32787
|
+
const open = await Promise.all(this.siblings(wt).map((w) => this.read(w)));
|
|
32788
|
+
const archived = await Promise.all(this.d.archived(wt.repoId).map((a) => this.readArchived(a)));
|
|
32789
|
+
open.sort((a, b) => b.active - a.active);
|
|
32790
|
+
if (words.length === 0) {
|
|
32791
|
+
const shown = [...open, ...archived.slice(0, ARCHIVED_MAX)];
|
|
32792
|
+
const more = archived.length - ARCHIVED_MAX;
|
|
32793
|
+
const head = `${open.length} other ${open.length === 1 ? "worktree" : "worktrees"} of this project ${open.length === 1 ? "is" : "are"} open and ${archived.length} ${archived.length === 1 ? "is" : "are"} archived.`;
|
|
32794
|
+
const tail = more > 0 ? `
|
|
32795
|
+
|
|
32796
|
+
${more} older archived ${more === 1 ? "worktree is" : "worktrees are"} not listed; pass a query to search them.` : "";
|
|
32797
|
+
return { text: shown.length ? `${head}
|
|
32798
|
+
|
|
32799
|
+
${shown.map((c) => row(c, [])).join(`
|
|
32800
|
+
|
|
32801
|
+
`)}${tail}` : head };
|
|
32802
|
+
}
|
|
32803
|
+
const hits = [...open, ...archived].filter((c) => matches(c, words));
|
|
32804
|
+
if (hits.length === 0) {
|
|
32805
|
+
return {
|
|
32806
|
+
text: `No worktree of this project matches "${query}". Searched ${open.length} open and ${archived.length} archived by title, branch and what the person wrote in each chat.`
|
|
32807
|
+
};
|
|
32808
|
+
}
|
|
32809
|
+
return {
|
|
32810
|
+
text: `${hits.length} ${hits.length === 1 ? "worktree" : "worktrees"} of this project ${hits.length === 1 ? "matches" : "match"} "${query}".
|
|
32811
|
+
|
|
32812
|
+
${hits.map((c) => row(c, words)).join(`
|
|
32813
|
+
|
|
32814
|
+
`)}`
|
|
32815
|
+
};
|
|
32816
|
+
}
|
|
32817
|
+
siblings(wt) {
|
|
32818
|
+
return this.d.state.worktrees.filter((w) => w.repoId === wt.repoId && w.id !== wt.id && w.kind === "worktree");
|
|
32819
|
+
}
|
|
32820
|
+
async read(w) {
|
|
32821
|
+
const transcript = transcriptPathFor(this.d.transcriptsDir, w.id);
|
|
32822
|
+
const said = await userMessages(transcript);
|
|
32823
|
+
return {
|
|
32824
|
+
sibling: { branch: w.branch, title: w.title, path: w.path, transcript },
|
|
32825
|
+
said,
|
|
32826
|
+
active: said.at(-1)?.ts ?? w.createdAt
|
|
32827
|
+
};
|
|
32828
|
+
}
|
|
32829
|
+
async readArchived(a) {
|
|
32830
|
+
const said = await userMessages(a.transcript);
|
|
32831
|
+
return {
|
|
32832
|
+
sibling: {
|
|
32833
|
+
branch: a.branch,
|
|
32834
|
+
title: a.title,
|
|
32835
|
+
path: a.path,
|
|
32836
|
+
transcript: a.transcript,
|
|
32837
|
+
archived: { at: a.archivedAt, landed: a.landed, restorable: a.restorable }
|
|
32838
|
+
},
|
|
32839
|
+
said,
|
|
32840
|
+
prompt: a.prompt,
|
|
32841
|
+
active: said.at(-1)?.ts ?? a.archivedAt
|
|
32842
|
+
};
|
|
32843
|
+
}
|
|
32844
|
+
}
|
|
32845
|
+
async function userMessages(transcript) {
|
|
32846
|
+
let text;
|
|
32847
|
+
try {
|
|
32848
|
+
text = await readFile3(transcript, "utf8");
|
|
32849
|
+
} catch {
|
|
32850
|
+
return [];
|
|
32851
|
+
}
|
|
32852
|
+
const out = [];
|
|
32853
|
+
for (const line of text.split(`
|
|
32854
|
+
`)) {
|
|
32855
|
+
if (!line.includes('"user-message"'))
|
|
32856
|
+
continue;
|
|
32857
|
+
try {
|
|
32858
|
+
const e = JSON.parse(line)?.event;
|
|
32859
|
+
if (e?.type === "user-message" && typeof e.text === "string")
|
|
32860
|
+
out.push({ text: e.text, ts: Number(e.ts) || 0 });
|
|
32861
|
+
} catch {}
|
|
32862
|
+
}
|
|
32863
|
+
return out;
|
|
32864
|
+
}
|
|
32865
|
+
function matches(c, words) {
|
|
32866
|
+
const corpus = [c.sibling.title, c.sibling.branch, c.prompt ?? "", ...c.said.map((s) => s.text)].join(`
|
|
32867
|
+
`).toLowerCase();
|
|
32868
|
+
return words.every((w) => corpus.includes(w));
|
|
32869
|
+
}
|
|
32870
|
+
function row(c, words) {
|
|
32871
|
+
const parts = [siblingParagraph(c.sibling)];
|
|
32872
|
+
const first = c.said[0]?.text ?? c.prompt;
|
|
32873
|
+
if (first)
|
|
32874
|
+
parts.push(`The person first asked: "${clip2(first)}"${first.length > ASKED_MAX ? " (cut there)." : ""}`);
|
|
32875
|
+
if (c.said.length > 0)
|
|
32876
|
+
parts.push(`They last wrote there on ${dayOf(c.active)}.`);
|
|
32877
|
+
if (words.length > 0 && c.said.length > 0) {
|
|
32878
|
+
const mentioning = c.said.filter((s) => {
|
|
32879
|
+
const t = s.text.toLowerCase();
|
|
32880
|
+
return words.some((w) => t.includes(w));
|
|
32881
|
+
}).length;
|
|
32882
|
+
if (mentioning > 0)
|
|
32883
|
+
parts.push(`${mentioning} of their ${c.said.length} messages mention the query.`);
|
|
32884
|
+
}
|
|
32885
|
+
return parts.join(" ");
|
|
32886
|
+
}
|
|
32887
|
+
function clip2(text) {
|
|
32888
|
+
const one = text.replace(/\s+/g, " ").trim();
|
|
32889
|
+
return one.length > ASKED_MAX ? one.slice(0, ASKED_MAX).trimEnd() : one;
|
|
32890
|
+
}
|
|
32891
|
+
|
|
32499
32892
|
// ../daemon/src/worktrees/pieces.ts
|
|
32500
32893
|
import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
|
|
32501
32894
|
import { readFileSync as readFileSync20 } from "fs";
|
|
@@ -32554,6 +32947,7 @@ var PROJECT_MAX = 200;
|
|
|
32554
32947
|
var refused = (text) => ({ text, isError: true });
|
|
32555
32948
|
var HELD = "Handed to the person; the list shows beside your plan and they decide where each piece goes. Ask for plan approval now.";
|
|
32556
32949
|
var SHOWN = "Handed to the person as a card; they decide. Finish your turn.";
|
|
32950
|
+
var SHOWN_HERE = "Handed to the person as a card. This worktree holds no work yet, so the card leads with doing it here; they decide. Finish your turn.";
|
|
32557
32951
|
function openPlanCard(entries) {
|
|
32558
32952
|
const open = new Map;
|
|
32559
32953
|
for (const { event } of entries) {
|
|
@@ -32727,7 +33121,7 @@ class PiecesService {
|
|
|
32727
33121
|
},
|
|
32728
33122
|
after: {
|
|
32729
33123
|
type: "string",
|
|
32730
|
-
description: 'worktree
|
|
33124
|
+
description: 'worktree: what the piece waits for before it starts: a branch, another piece of this list by its title, or "this" for your own branch. here: what your own work on the piece waits for, a branch or another piece of this list; Toyon shows the wait under the box until it lands.'
|
|
32731
33125
|
},
|
|
32732
33126
|
to: { type: "string", description: "send only: the open worktree it goes to, by branch or title." },
|
|
32733
33127
|
project: {
|
|
@@ -32812,19 +33206,35 @@ class PiecesService {
|
|
|
32812
33206
|
return refused(`Piece ${i + 1} ("${title}"): ${found.refused}`);
|
|
32813
33207
|
piece.project = { id: found.repo.id, name: found.repo.name };
|
|
32814
33208
|
}
|
|
32815
|
-
if (p.where
|
|
32816
|
-
return refused(`Piece ${i + 1} ("${title}") has \`after\`, which
|
|
33209
|
+
if (p.where === "send" && p.after !== undefined) {
|
|
33210
|
+
return refused(`Piece ${i + 1} ("${title}") has \`after\`, which a \`send\` piece does not take.`);
|
|
32817
33211
|
}
|
|
32818
33212
|
if (p.where !== "send" && p.to !== undefined) {
|
|
32819
33213
|
return refused(`Piece ${i + 1} ("${title}") has \`to\`, which only a \`send\` piece takes.`);
|
|
32820
33214
|
}
|
|
32821
|
-
if (p.
|
|
33215
|
+
if (p.after !== undefined) {
|
|
32822
33216
|
const q = p.after.trim();
|
|
32823
33217
|
if (!q)
|
|
32824
33218
|
return refused(`Piece ${i + 1} ("${title}") has a blank \`after\`; name what it waits for, or leave it out.`);
|
|
32825
|
-
const after = this.resolveAfter(wt, raw, i, q);
|
|
33219
|
+
const after = this.resolveAfter(wt, raw, i, q, p.where === "here");
|
|
32826
33220
|
if ("refused" in after)
|
|
32827
33221
|
return refused(`Piece ${i + 1} ("${title}"): ${after.refused}`);
|
|
33222
|
+
if (p.where === "here" && !("piece" in after.after) && after.after.id === wt.id) {
|
|
33223
|
+
return refused(`Piece ${i + 1} ("${title}") is \`here\` and waits for its own branch.`);
|
|
33224
|
+
}
|
|
33225
|
+
if (p.where === "here" && "piece" in after.after) {
|
|
33226
|
+
const on = raw[Number(after.after.piece.slice(1)) - 1];
|
|
33227
|
+
const onTitle = sentenceCase(on.title.trim());
|
|
33228
|
+
if (on.where === "here") {
|
|
33229
|
+
return refused(`Piece ${i + 1} ("${title}") waits for "${onTitle}", which is \`here\` too and lands with it.`);
|
|
33230
|
+
}
|
|
33231
|
+
const back = on.after?.trim() ?? "";
|
|
33232
|
+
const bare = back.startsWith("@") ? back.slice(1) : back;
|
|
33233
|
+
const onOrigin = bare !== "" && (bare === "this" || bare === wt.branch || raw.some((o) => o.where === "here" && matchBy([o], bare, [(x) => x.title.trim()]).length > 0));
|
|
33234
|
+
if (onOrigin) {
|
|
33235
|
+
return refused(`Piece ${i + 1} ("${title}") waits for "${onTitle}", which waits for this branch: a cycle.`);
|
|
33236
|
+
}
|
|
33237
|
+
}
|
|
32828
33238
|
piece.after = after.after;
|
|
32829
33239
|
}
|
|
32830
33240
|
if (p.where === "send") {
|
|
@@ -32850,6 +33260,9 @@ class PiecesService {
|
|
|
32850
33260
|
agent.note({ type: "pieces-declined", id: o.id, reason: "handed over again", ts: Date.now() });
|
|
32851
33261
|
}
|
|
32852
33262
|
const held = by !== "person" && wt.mode === "plan";
|
|
33263
|
+
const here = by === "agent" && !held && this.offerHere(wt, pieces);
|
|
33264
|
+
if (here)
|
|
33265
|
+
pieces[0].fate = "here";
|
|
32853
33266
|
const plan = held ? null : writePlanDocNow(wt.path, listDoc(pieces));
|
|
32854
33267
|
agent.note({
|
|
32855
33268
|
type: "pieces-proposed",
|
|
@@ -32862,18 +33275,30 @@ class PiecesService {
|
|
|
32862
33275
|
...by === "toyon" && read ? { read } : {},
|
|
32863
33276
|
ts: Date.now()
|
|
32864
33277
|
});
|
|
32865
|
-
return { text: held ? HELD : SHOWN };
|
|
33278
|
+
return { text: held ? HELD : here ? SHOWN_HERE : SHOWN };
|
|
32866
33279
|
}
|
|
32867
|
-
|
|
33280
|
+
offerHere(wt, pieces) {
|
|
33281
|
+
if (pieces.length !== 1)
|
|
33282
|
+
return false;
|
|
33283
|
+
const p = pieces[0];
|
|
33284
|
+
if (p.fate !== "worktree" || p.project || p.to || p.after)
|
|
33285
|
+
return false;
|
|
33286
|
+
const counts = this.d.counts(wt.id);
|
|
33287
|
+
return counts?.dirty === 0 && counts.ahead === 0;
|
|
33288
|
+
}
|
|
33289
|
+
resolveAfter(wt, raw, at, q, anywhere = false) {
|
|
32868
33290
|
const bare = q.startsWith("@") ? q.slice(1) : q;
|
|
32869
33291
|
const titled = raw.map((p, i) => ({ i, title: p.title.trim() }));
|
|
32870
|
-
const onList = matchBy(titled.filter((p) => p.i < at), bare, [(p) => p.title]);
|
|
33292
|
+
const onList = matchBy(titled.filter((p) => anywhere ? p.i !== at : p.i < at), bare, [(p) => p.title]);
|
|
32871
33293
|
if (onList.length === 1)
|
|
32872
33294
|
return { after: { piece: `p${onList[0].i + 1}` } };
|
|
32873
33295
|
if (onList.length > 1)
|
|
32874
33296
|
return { refused: `"${q}" names more than one piece on the list; give them different titles.` };
|
|
32875
|
-
|
|
32876
|
-
|
|
33297
|
+
const rest = matchBy(titled.filter((p) => anywhere ? p.i === at : p.i >= at), bare, [(p) => p.title]);
|
|
33298
|
+
if (rest.length > 0) {
|
|
33299
|
+
return {
|
|
33300
|
+
refused: anywhere ? `"${q}" is this piece itself.` : `"${q}" is this piece or a later one; a piece waits only for one earlier on the list.`
|
|
33301
|
+
};
|
|
32877
33302
|
}
|
|
32878
33303
|
if (bare === "this" || bare === wt.branch)
|
|
32879
33304
|
return { after: { id: wt.id, branch: wt.branch, title: wt.title } };
|
|
@@ -32912,6 +33337,13 @@ class PiecesService {
|
|
|
32912
33337
|
throw new UserError("That piece is not on the list");
|
|
32913
33338
|
if (fate === "send" && !piece.to)
|
|
32914
33339
|
throw new UserError("That piece has no worktree to send to");
|
|
33340
|
+
const after = piece.after;
|
|
33341
|
+
if (fate === "worktree" && after && "piece" in after) {
|
|
33342
|
+
const at = proposed.pieces.findIndex((p) => p.id === pieceId);
|
|
33343
|
+
if (proposed.pieces.findIndex((p) => p.id === after.piece) > at) {
|
|
33344
|
+
throw new UserError("That piece waits for one later on the list, so it is done here or skipped");
|
|
33345
|
+
}
|
|
33346
|
+
}
|
|
32915
33347
|
if (piece.fate === "send" && (fate === "here" || fate === "worktree")) {
|
|
32916
33348
|
throw new UserError("A piece for an open worktree is sent there or skipped");
|
|
32917
33349
|
}
|
|
@@ -32969,6 +33401,7 @@ class PiecesService {
|
|
|
32969
33401
|
}
|
|
32970
33402
|
}
|
|
32971
33403
|
agent.note({ type: "pieces", id, outcomes, ts: Date.now() });
|
|
33404
|
+
this.recordAwaits(wt.id, entries, proposed, became);
|
|
32972
33405
|
if (tell && outcomes.some((o) => o.fate !== "here")) {
|
|
32973
33406
|
for (let i = 0;i < 30 && openPlanCard(agent.transcript()); i++)
|
|
32974
33407
|
await Bun.sleep(100);
|
|
@@ -33105,7 +33538,9 @@ ${p.section}`;
|
|
|
33105
33538
|
const crossed = piece.project ? { repoName: piece.project.name } : {};
|
|
33106
33539
|
if (fate === "here" || fate === "skip") {
|
|
33107
33540
|
became.set(piece.id, { fate });
|
|
33108
|
-
|
|
33541
|
+
const waits = fate === "here" ? piece.after : undefined;
|
|
33542
|
+
const after = !waits ? undefined : ("piece" in waits) ? proposed.pieces.find((p) => p.id === waits.piece)?.title ?? waits.piece : waits.branch;
|
|
33543
|
+
return { pieceId: piece.id, fate, ...after ? { after } : {} };
|
|
33109
33544
|
}
|
|
33110
33545
|
if (fate === "send") {
|
|
33111
33546
|
const target = piece.to ? this.d.state.worktree(piece.to.id) : undefined;
|
|
@@ -33152,6 +33587,23 @@ ${p.section}`;
|
|
|
33152
33587
|
became.set(piece.id, { fate, worktree: { id: made.id, branch: made.branch, title: made.title } });
|
|
33153
33588
|
return { pieceId: piece.id, fate, worktreeId: made.id, title: made.title, ...crossed };
|
|
33154
33589
|
}
|
|
33590
|
+
recordAwaits(originId, entries, proposed, became) {
|
|
33591
|
+
const awaits = [];
|
|
33592
|
+
for (const piece of proposed.pieces) {
|
|
33593
|
+
const after = piece.after;
|
|
33594
|
+
if (!after || effectiveFate(entries, proposed, piece) !== "here")
|
|
33595
|
+
continue;
|
|
33596
|
+
const w = "piece" in after ? became.get(after.piece)?.worktree : after;
|
|
33597
|
+
if (w && !awaits.some((a) => a.id === w.id))
|
|
33598
|
+
awaits.push(w);
|
|
33599
|
+
}
|
|
33600
|
+
if (awaits.length === 0)
|
|
33601
|
+
return;
|
|
33602
|
+
const live = this.d.state.requireWorktree(originId);
|
|
33603
|
+
live.awaits = [...(live.awaits ?? []).filter((a) => !awaits.some((b) => b.id === a.id)), ...awaits];
|
|
33604
|
+
this.d.state.save();
|
|
33605
|
+
this.d.hub.emit("worktreesChanged");
|
|
33606
|
+
}
|
|
33155
33607
|
waitsFor(wt, piece, became) {
|
|
33156
33608
|
const after = piece.after;
|
|
33157
33609
|
if (!after)
|
|
@@ -33216,7 +33668,10 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
33216
33668
|
return out;
|
|
33217
33669
|
}
|
|
33218
33670
|
async landed(landedId) {
|
|
33219
|
-
|
|
33671
|
+
const told = new Set;
|
|
33672
|
+
const origin = this.tellOrigin(landedId);
|
|
33673
|
+
if (origin)
|
|
33674
|
+
told.add(origin);
|
|
33220
33675
|
const madeNow = new Set;
|
|
33221
33676
|
for (const origin of this.d.state.worktrees) {
|
|
33222
33677
|
const due = origin.queued?.filter((q) => !("piece" in q.after) && q.after.id === landedId) ?? [];
|
|
@@ -33253,7 +33708,38 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
33253
33708
|
}
|
|
33254
33709
|
}
|
|
33255
33710
|
}
|
|
33256
|
-
this.tellWaiters(landedId, madeNow)
|
|
33711
|
+
for (const id of this.tellWaiters(landedId, madeNow))
|
|
33712
|
+
told.add(id);
|
|
33713
|
+
this.tellAwaiting(landedId, told);
|
|
33714
|
+
}
|
|
33715
|
+
tellAwaiting(landedId, told) {
|
|
33716
|
+
const landed = this.d.state.worktree(landedId);
|
|
33717
|
+
const repo = landed ? this.d.state.repo(landed.repoId) : undefined;
|
|
33718
|
+
let changed = false;
|
|
33719
|
+
for (const w of this.d.state.worktrees) {
|
|
33720
|
+
if (!w.awaits?.some((a) => a.id === landedId))
|
|
33721
|
+
continue;
|
|
33722
|
+
w.awaits = w.awaits.filter((a) => a.id !== landedId);
|
|
33723
|
+
if (w.awaits.length === 0)
|
|
33724
|
+
delete w.awaits;
|
|
33725
|
+
changed = true;
|
|
33726
|
+
if (!landed || !repo || w.kind !== "worktree" || told.has(w.id))
|
|
33727
|
+
continue;
|
|
33728
|
+
this.d.runtime.ensureAgent(w).agent.note({
|
|
33729
|
+
type: "piece-landed",
|
|
33730
|
+
worktreeId: landed.id,
|
|
33731
|
+
repoName: repo.name,
|
|
33732
|
+
title: landed.title,
|
|
33733
|
+
...landed.pr?.url ? { url: landed.pr.url } : {},
|
|
33734
|
+
...w.repoId === landed.repoId ? { same: true } : {},
|
|
33735
|
+
awaited: true,
|
|
33736
|
+
ts: Date.now()
|
|
33737
|
+
});
|
|
33738
|
+
}
|
|
33739
|
+
if (changed) {
|
|
33740
|
+
this.d.state.save();
|
|
33741
|
+
this.d.hub.emit("worktreesChanged");
|
|
33742
|
+
}
|
|
33257
33743
|
}
|
|
33258
33744
|
repoint(origin, made, worktree) {
|
|
33259
33745
|
const live = this.d.state.worktree(origin.id);
|
|
@@ -33298,13 +33784,13 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
33298
33784
|
tellOrigin(landedId) {
|
|
33299
33785
|
const wt = this.d.state.worktree(landedId);
|
|
33300
33786
|
if (wt?.from?.kind !== "worktree")
|
|
33301
|
-
return;
|
|
33787
|
+
return null;
|
|
33302
33788
|
const origin = this.d.state.worktree(wt.from.origin.id);
|
|
33303
33789
|
if (origin?.kind !== "worktree")
|
|
33304
|
-
return;
|
|
33790
|
+
return null;
|
|
33305
33791
|
const repo = this.d.state.repo(wt.repoId);
|
|
33306
33792
|
if (!repo)
|
|
33307
|
-
return;
|
|
33793
|
+
return null;
|
|
33308
33794
|
this.d.runtime.ensureAgent(origin).agent.note({
|
|
33309
33795
|
type: "piece-landed",
|
|
33310
33796
|
worktreeId: wt.id,
|
|
@@ -33314,19 +33800,22 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
33314
33800
|
...wt.repoId === origin.repoId ? { same: true } : {},
|
|
33315
33801
|
ts: Date.now()
|
|
33316
33802
|
});
|
|
33803
|
+
return origin.id;
|
|
33317
33804
|
}
|
|
33318
33805
|
tellWaiters(landedId, madeNow) {
|
|
33319
33806
|
const landed = this.d.state.worktree(landedId);
|
|
33320
33807
|
if (!landed)
|
|
33321
|
-
return;
|
|
33808
|
+
return [];
|
|
33322
33809
|
const repo = this.d.state.repo(landed.repoId);
|
|
33323
33810
|
if (!repo)
|
|
33324
|
-
return;
|
|
33811
|
+
return [];
|
|
33812
|
+
const told = [];
|
|
33325
33813
|
for (const w of this.d.state.worktrees) {
|
|
33326
33814
|
if (w.kind !== "worktree" || w.landed || w.id === landedId || madeNow.has(w.id))
|
|
33327
33815
|
continue;
|
|
33328
33816
|
if (w.from?.kind !== "worktree" || w.from.after?.id !== landedId)
|
|
33329
33817
|
continue;
|
|
33818
|
+
told.push(w.id);
|
|
33330
33819
|
this.d.runtime.ensureAgent(w).agent.note({
|
|
33331
33820
|
type: "piece-landed",
|
|
33332
33821
|
worktreeId: landed.id,
|
|
@@ -33338,6 +33827,7 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
33338
33827
|
ts: Date.now()
|
|
33339
33828
|
});
|
|
33340
33829
|
}
|
|
33830
|
+
return told;
|
|
33341
33831
|
}
|
|
33342
33832
|
sweep() {
|
|
33343
33833
|
let changed = false;
|
|
@@ -35705,7 +36195,7 @@ var idsOf = (rows, trunks) => [
|
|
|
35705
36195
|
...rows.map((r) => r.id),
|
|
35706
36196
|
...Object.values(trunks).map((t) => t.id)
|
|
35707
36197
|
];
|
|
35708
|
-
var
|
|
36198
|
+
var heldBy2 = (found) => `${found.name} is held by another tool${found.lockReason ? `: ${found.lockReason}` : ""}`;
|
|
35709
36199
|
var commandLine = (args) => args.map((a) => /^[\w./:=@,+-]+$/.test(a) ? a : JSON.stringify(a.split(`
|
|
35710
36200
|
`, 1)[0])).join(" ");
|
|
35711
36201
|
|
|
@@ -35821,7 +36311,7 @@ class WorktreeService {
|
|
|
35821
36311
|
const task = taskText(prompt, attachments);
|
|
35822
36312
|
const given = cleanTitle2(opts.title ?? "") || undefined;
|
|
35823
36313
|
const title = given ?? titleFrom(task);
|
|
35824
|
-
const spare = this.d.state.worktrees.find((w) => w.repoId === repoId && w.kind === "spare");
|
|
36314
|
+
const spare = opts.cold ? undefined : this.d.state.worktrees.find((w) => w.repoId === repoId && w.kind === "spare");
|
|
35825
36315
|
const restart = !!spare && profile !== undefined && profile !== resolveRun(repo, spare).profile;
|
|
35826
36316
|
let carriedIn;
|
|
35827
36317
|
let release;
|
|
@@ -35835,7 +36325,7 @@ class WorktreeService {
|
|
|
35835
36325
|
profile,
|
|
35836
36326
|
unnamed: given ? undefined : true
|
|
35837
36327
|
};
|
|
35838
|
-
const
|
|
36328
|
+
const prepare = async (wt) => {
|
|
35839
36329
|
const rt = this.d.runtime.ensureAgent(wt);
|
|
35840
36330
|
release = rt.agent.hold();
|
|
35841
36331
|
if (rt.agent.runningAgent !== null && rt.agent.runningAgent !== agent)
|
|
@@ -35847,7 +36337,8 @@ class WorktreeService {
|
|
|
35847
36337
|
attachments,
|
|
35848
36338
|
...born ? { handed: born.handed } : {}
|
|
35849
36339
|
});
|
|
35850
|
-
}
|
|
36340
|
+
};
|
|
36341
|
+
const claimed = spare ? await this.spare.claim(repoId, title, opts.worktreeId, stamps, prepare) : null;
|
|
35851
36342
|
if (claimed) {
|
|
35852
36343
|
const { wt } = claimed;
|
|
35853
36344
|
const rt = this.d.runtime.ensureAgent(wt);
|
|
@@ -36064,7 +36555,7 @@ class WorktreeService {
|
|
|
36064
36555
|
const target = canonical(r.path);
|
|
36065
36556
|
const pushed = this.discoveredById(worktreeId);
|
|
36066
36557
|
if (pushed?.locked)
|
|
36067
|
-
throw new UserError(
|
|
36558
|
+
throw new UserError(heldBy2(pushed));
|
|
36068
36559
|
if (!r.branch)
|
|
36069
36560
|
throw new UserError(`${r.name} is detached: check out a branch in it first`);
|
|
36070
36561
|
const enclosing = [...this.d.state.repos, ...this.d.state.worktrees].find((w) => isInside(target, canonical(w.path)));
|
|
@@ -36091,7 +36582,7 @@ class WorktreeService {
|
|
|
36091
36582
|
await withRepoLock(repo.path, async () => {
|
|
36092
36583
|
const found = await this.requireDiscovered(repoId, repo.path, target, wt.id);
|
|
36093
36584
|
if (found.locked)
|
|
36094
|
-
throw new UserError(
|
|
36585
|
+
throw new UserError(heldBy2(found));
|
|
36095
36586
|
if (!found.branch)
|
|
36096
36587
|
throw new UserError(`${found.name} is detached: check out a branch in it first`);
|
|
36097
36588
|
wt.branch = found.branch;
|
|
@@ -36960,7 +37451,7 @@ ${wt.landing.body}` : s;
|
|
|
36960
37451
|
return this.landOp(wt, repo, message);
|
|
36961
37452
|
});
|
|
36962
37453
|
if (out.result.ok && out.landed)
|
|
36963
|
-
this.noteLanded(wt, out.result.message, out.mark);
|
|
37454
|
+
this.noteLanded(wt, out.result.message, out.mark, out.url);
|
|
36964
37455
|
return { result: this.fixShip(wt, out.result, baseOf(repo)) };
|
|
36965
37456
|
}
|
|
36966
37457
|
fixShip(wt, result, base) {
|
|
@@ -37005,8 +37496,14 @@ ${wt.landing.body}` : s;
|
|
|
37005
37496
|
return null;
|
|
37006
37497
|
return { ok: false, message: "the agent has changed files since the check: land once its turn ends" };
|
|
37007
37498
|
}
|
|
37008
|
-
noteLanded(wt, message, mark) {
|
|
37009
|
-
this.d.runtime.ensureAgent(wt).agent.note({
|
|
37499
|
+
noteLanded(wt, message, mark, url) {
|
|
37500
|
+
this.d.runtime.ensureAgent(wt).agent.note({
|
|
37501
|
+
type: "landed",
|
|
37502
|
+
message,
|
|
37503
|
+
...mark ? { mark } : {},
|
|
37504
|
+
...url ? { url } : {},
|
|
37505
|
+
ts: Date.now()
|
|
37506
|
+
});
|
|
37010
37507
|
this.d.hub.emit("landed", wt.id);
|
|
37011
37508
|
}
|
|
37012
37509
|
async followBranch(wt, current) {
|
|
@@ -37067,7 +37564,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
37067
37564
|
if (policy.land === "pr") {
|
|
37068
37565
|
if (wt.pr?.state === "merged" && !prTaken(wt)) {
|
|
37069
37566
|
const { mark, ...result } = await this.prMergedOp(wt.id, undefined, w);
|
|
37070
|
-
return result.ok ? { result, landed: true, mark } : { result };
|
|
37567
|
+
return result.ok ? { result, landed: true, mark, url: wt.pr.url } : { result };
|
|
37071
37568
|
}
|
|
37072
37569
|
const fetched = await this.fetchBase(wt, repo, w);
|
|
37073
37570
|
if (!fetched.ok)
|
|
@@ -37107,7 +37604,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
37107
37604
|
const { mark: noted, ...r } = await this.prMergedOp(wt.id, mark, w);
|
|
37108
37605
|
const on = `origin's ${repo.defaultBranch} has this work already`;
|
|
37109
37606
|
const result = { ...r, message: r.ok ? `${on}; ${r.message}` : `${on}, but ${r.message}` };
|
|
37110
|
-
return r.ok ? { result, landed: true, mark: noted } : { result };
|
|
37607
|
+
return r.ok ? { result, landed: true, mark: noted, url: wt.pr?.url } : { result };
|
|
37111
37608
|
}
|
|
37112
37609
|
const named = !suggested ? undefined : splitMessage(policy.merge === "squash" ? await squashMessage(wt.path, base, suggested, !!typed) : suggested);
|
|
37113
37610
|
const result = await openPr({
|
|
@@ -37296,7 +37793,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
37296
37793
|
else
|
|
37297
37794
|
log.warn(worktreeId, `landing failed: ${result.message}`);
|
|
37298
37795
|
if (result.ok && wt)
|
|
37299
|
-
this.noteLanded(wt, result.message, mark);
|
|
37796
|
+
this.noteLanded(wt, result.message, mark, wt.pr?.url);
|
|
37300
37797
|
return result;
|
|
37301
37798
|
}
|
|
37302
37799
|
async prMergedOp(worktreeId, carried, w = UNWATCHED) {
|
|
@@ -37662,6 +38159,16 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
37662
38159
|
}
|
|
37663
38160
|
return this.d.state.requireWorktree(worktreeId);
|
|
37664
38161
|
}
|
|
38162
|
+
writtenTranscript(worktreeId) {
|
|
38163
|
+
const path = transcriptPathFor(this.d.paths.transcriptsDir, worktreeId);
|
|
38164
|
+
return existsSync36(path) ? path : undefined;
|
|
38165
|
+
}
|
|
38166
|
+
transcriptPath(id) {
|
|
38167
|
+
const path = this.d.state.worktree(id) ? this.writtenTranscript(id) : this.archive.chatFiles(id)?.transcript;
|
|
38168
|
+
if (!path || !existsSync36(path))
|
|
38169
|
+
throw new UserError("nothing has been written to this chat yet");
|
|
38170
|
+
return path;
|
|
38171
|
+
}
|
|
37665
38172
|
async send(worktreeId, msg) {
|
|
37666
38173
|
await this.archivingNow(worktreeId);
|
|
37667
38174
|
if (!this.d.state.worktree(worktreeId) && this.hasArchived(worktreeId)) {
|
|
@@ -37755,6 +38262,8 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
37755
38262
|
borrowed: rt?.borrowed.size ? [...rt.borrowed] : undefined,
|
|
37756
38263
|
agent: rt?.agent.status ?? "idle",
|
|
37757
38264
|
stopping: rt?.agent.stopping || undefined,
|
|
38265
|
+
background: rt?.agent.holding || undefined,
|
|
38266
|
+
wakes: rt?.agent.wakes ?? undefined,
|
|
37758
38267
|
login: !!rt?.login,
|
|
37759
38268
|
ahead,
|
|
37760
38269
|
behind,
|
|
@@ -37763,7 +38272,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
37763
38272
|
queued: rt?.agent.queueLength || undefined,
|
|
37764
38273
|
unseen: isUnseen(wt) || undefined,
|
|
37765
38274
|
usage: this.usageFor(wt.id),
|
|
37766
|
-
transcript:
|
|
38275
|
+
transcript: this.writtenTranscript(wt.id),
|
|
37767
38276
|
sessionId: this.d.state.session(wt.id),
|
|
37768
38277
|
shipping: this.ops.get(wt.id),
|
|
37769
38278
|
syncQueued: this.syncQueued.has(wt.id) || undefined
|
|
@@ -38015,9 +38524,11 @@ var pieces = new PiecesService({
|
|
|
38015
38524
|
runtime,
|
|
38016
38525
|
attachments: attachments2,
|
|
38017
38526
|
create: (repoId, prompt, opts) => worktrees.create(repoId, prompt, opts),
|
|
38527
|
+
counts: (id) => worktrees.tree.counts(id),
|
|
38018
38528
|
reader: makePiecesReader(runtime, agents, state)
|
|
38019
38529
|
});
|
|
38020
38530
|
tools.add(pieces);
|
|
38531
|
+
tools.add(new WorktreeLookup({ state, transcriptsDir: paths.transcriptsDir, archived: (repoId) => worktrees.archived(repoId) }));
|
|
38021
38532
|
var turns = new TurnService({
|
|
38022
38533
|
state,
|
|
38023
38534
|
hub,
|
|
@@ -38127,6 +38638,8 @@ var keepAwake = new KeepAwake({
|
|
|
38127
38638
|
hub,
|
|
38128
38639
|
demand: () => runtime.demand(),
|
|
38129
38640
|
assert: idleSleepAssertion(process.env.TOYON_KEEP_AWAKE),
|
|
38641
|
+
bridge: idleSleepBridge(process.env.TOYON_KEEP_AWAKE),
|
|
38642
|
+
carried: process.env[CARRIED_ENV] === "1",
|
|
38130
38643
|
mode: () => state.keepAwake,
|
|
38131
38644
|
answerable: () => setting.get() !== null
|
|
38132
38645
|
});
|
|
@@ -38259,7 +38772,7 @@ async function shutdown(signal, opts = {}) {
|
|
|
38259
38772
|
shuttingDown = true;
|
|
38260
38773
|
log.info("daemon", `${signal}: stopping dev servers`);
|
|
38261
38774
|
idleExit.stop();
|
|
38262
|
-
keepAwake.stop();
|
|
38775
|
+
const heldAwake = keepAwake.stop({ handoff: opts.respawn === true });
|
|
38263
38776
|
tailnet.stop();
|
|
38264
38777
|
idle.shutdown();
|
|
38265
38778
|
landing.stop();
|
|
@@ -38280,7 +38793,7 @@ async function shutdown(signal, opts = {}) {
|
|
|
38280
38793
|
await Bun.sleep(250);
|
|
38281
38794
|
log.info("daemon", "starting the replacement");
|
|
38282
38795
|
try {
|
|
38283
|
-
respawn(paths.logFile);
|
|
38796
|
+
respawn(paths.logFile, carryEnv(process.env, heldAwake));
|
|
38284
38797
|
} catch (e) {
|
|
38285
38798
|
log.error("daemon", "the replacement did not start; run `toyon` to start it again", e);
|
|
38286
38799
|
process.exit(1);
|