toyon 0.24.0 → 0.25.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 +36 -0
- package/dist/cli.js +1 -1
- package/dist/daemon.js +659 -213
- package/dist/shell/assets/{Editor-Dq4a0gFv.js → Editor-v7DPr3mY.js} +82 -82
- package/dist/shell/assets/Editor-v7DPr3mY.js.br +0 -0
- package/dist/shell/assets/Editor-v7DPr3mY.js.gz +0 -0
- package/dist/shell/assets/{XTerm-BfuBg96X.js → XTerm-CxU9R8q7.js} +1 -1
- package/dist/shell/assets/XTerm-CxU9R8q7.js.br +0 -0
- package/dist/shell/assets/XTerm-CxU9R8q7.js.gz +0 -0
- package/dist/shell/assets/{cssMode-An1EUPnJ.js → cssMode-DD5SBqGU.js} +1 -1
- package/dist/shell/assets/cssMode-DD5SBqGU.js.br +0 -0
- package/dist/shell/assets/cssMode-DD5SBqGU.js.gz +0 -0
- package/dist/shell/assets/{freemarker2-PYOY2M7v.js → freemarker2-CZ3XZhjv.js} +1 -1
- package/dist/shell/assets/freemarker2-CZ3XZhjv.js.br +0 -0
- package/dist/shell/assets/freemarker2-CZ3XZhjv.js.gz +0 -0
- package/dist/shell/assets/{grammar-e2yQ-Xdq.js → grammar-DqAbMw7d.js} +1 -1
- package/dist/shell/assets/grammar-DqAbMw7d.js.br +0 -0
- package/dist/shell/assets/grammar-DqAbMw7d.js.gz +0 -0
- package/dist/shell/assets/{handlebars-BtSTwhBl.js → handlebars-CzZa_iMI.js} +1 -1
- package/dist/shell/assets/handlebars-CzZa_iMI.js.br +0 -0
- package/dist/shell/assets/handlebars-CzZa_iMI.js.gz +0 -0
- package/dist/shell/assets/{html-lmman0qi.js → html-uU_fKUCO.js} +1 -1
- package/dist/shell/assets/html-uU_fKUCO.js.br +0 -0
- package/dist/shell/assets/html-uU_fKUCO.js.gz +0 -0
- package/dist/shell/assets/{htmlMode-CYUUrmIv.js → htmlMode-DmuqvqLQ.js} +1 -1
- package/dist/shell/assets/htmlMode-DmuqvqLQ.js.br +0 -0
- package/dist/shell/assets/htmlMode-DmuqvqLQ.js.gz +0 -0
- package/dist/shell/assets/index-DMZqXgxq.js +295 -0
- package/dist/shell/assets/index-DMZqXgxq.js.br +0 -0
- package/dist/shell/assets/index-DMZqXgxq.js.gz +0 -0
- package/dist/shell/assets/index-LFCDUSQF.css +1 -0
- package/dist/shell/assets/index-LFCDUSQF.css.br +0 -0
- package/dist/shell/assets/index-LFCDUSQF.css.gz +0 -0
- package/dist/shell/assets/{javascript-CHVOacyY.js → javascript-CvDukmsa.js} +1 -1
- package/dist/shell/assets/{jsonMode-WaLJvHyp.js → jsonMode-C8ZGvKb7.js} +1 -1
- package/dist/shell/assets/jsonMode-C8ZGvKb7.js.br +0 -0
- package/dist/shell/assets/jsonMode-C8ZGvKb7.js.gz +0 -0
- package/dist/shell/assets/{liquid-Bhzd8TLd.js → liquid-CSFER4cS.js} +1 -1
- package/dist/shell/assets/liquid-CSFER4cS.js.br +0 -0
- package/dist/shell/assets/liquid-CSFER4cS.js.gz +0 -0
- package/dist/shell/assets/{lspLanguageFeatures-DS22S_fR.js → lspLanguageFeatures-Bnq8PCF_.js} +1 -1
- package/dist/shell/assets/lspLanguageFeatures-Bnq8PCF_.js.br +0 -0
- package/dist/shell/assets/lspLanguageFeatures-Bnq8PCF_.js.gz +0 -0
- package/dist/shell/assets/{mdx-1x45Dt5R.js → mdx-DN7UGs67.js} +1 -1
- package/dist/shell/assets/mdx-DN7UGs67.js.br +0 -0
- package/dist/shell/assets/mdx-DN7UGs67.js.gz +0 -0
- package/dist/shell/assets/{python-B0fHYRyj.js → python-Cdtmy77f.js} +1 -1
- package/dist/shell/assets/python-Cdtmy77f.js.br +0 -0
- package/dist/shell/assets/python-Cdtmy77f.js.gz +0 -0
- package/dist/shell/assets/{razor-C48vGnF3.js → razor-BzMJ8H5n.js} +1 -1
- package/dist/shell/assets/razor-BzMJ8H5n.js.br +0 -0
- package/dist/shell/assets/razor-BzMJ8H5n.js.gz +0 -0
- package/dist/shell/assets/toyon-mono-bold-1tbrivW1.woff2 +0 -0
- package/dist/shell/assets/toyon-mono-regular-BUtE4EFW.woff2 +0 -0
- package/dist/shell/assets/{tsMode-2SIN2sKE.js → tsMode-B_VeTgWt.js} +1 -1
- package/dist/shell/assets/tsMode-B_VeTgWt.js.br +0 -0
- package/dist/shell/assets/tsMode-B_VeTgWt.js.gz +0 -0
- package/dist/shell/assets/{xml-DPJN-pu8.js → xml-Cdv7rs1C.js} +1 -1
- package/dist/shell/assets/xml-Cdv7rs1C.js.br +0 -0
- package/dist/shell/assets/xml-Cdv7rs1C.js.gz +0 -0
- package/dist/shell/assets/{yaml-CDzmW7EM.js → yaml-Co_E2Q5j.js} +1 -1
- package/dist/shell/assets/yaml-Co_E2Q5j.js.br +0 -0
- package/dist/shell/assets/yaml-Co_E2Q5j.js.gz +0 -0
- package/dist/shell/index.html +2 -2
- package/package.json +1 -1
- package/dist/shell/assets/Editor-Dq4a0gFv.js.br +0 -0
- package/dist/shell/assets/Editor-Dq4a0gFv.js.gz +0 -0
- package/dist/shell/assets/XTerm-BfuBg96X.js.br +0 -0
- package/dist/shell/assets/XTerm-BfuBg96X.js.gz +0 -0
- package/dist/shell/assets/cssMode-An1EUPnJ.js.br +0 -0
- package/dist/shell/assets/cssMode-An1EUPnJ.js.gz +0 -0
- package/dist/shell/assets/freemarker2-PYOY2M7v.js.br +0 -0
- package/dist/shell/assets/freemarker2-PYOY2M7v.js.gz +0 -0
- package/dist/shell/assets/grammar-e2yQ-Xdq.js.br +0 -0
- package/dist/shell/assets/grammar-e2yQ-Xdq.js.gz +0 -0
- package/dist/shell/assets/handlebars-BtSTwhBl.js.br +0 -0
- package/dist/shell/assets/handlebars-BtSTwhBl.js.gz +0 -0
- package/dist/shell/assets/html-lmman0qi.js.br +0 -0
- package/dist/shell/assets/html-lmman0qi.js.gz +0 -0
- package/dist/shell/assets/htmlMode-CYUUrmIv.js.br +0 -0
- package/dist/shell/assets/htmlMode-CYUUrmIv.js.gz +0 -0
- package/dist/shell/assets/index-BDzHAr7c.css +0 -1
- package/dist/shell/assets/index-BDzHAr7c.css.br +0 -0
- package/dist/shell/assets/index-BDzHAr7c.css.gz +0 -0
- package/dist/shell/assets/index-o7SjsIYr.js +0 -294
- package/dist/shell/assets/index-o7SjsIYr.js.br +0 -0
- package/dist/shell/assets/index-o7SjsIYr.js.gz +0 -0
- package/dist/shell/assets/jsonMode-WaLJvHyp.js.br +0 -0
- package/dist/shell/assets/jsonMode-WaLJvHyp.js.gz +0 -0
- package/dist/shell/assets/liquid-Bhzd8TLd.js.br +0 -0
- package/dist/shell/assets/liquid-Bhzd8TLd.js.gz +0 -0
- package/dist/shell/assets/lspLanguageFeatures-DS22S_fR.js.br +0 -0
- package/dist/shell/assets/lspLanguageFeatures-DS22S_fR.js.gz +0 -0
- package/dist/shell/assets/mdx-1x45Dt5R.js.br +0 -0
- package/dist/shell/assets/mdx-1x45Dt5R.js.gz +0 -0
- package/dist/shell/assets/python-B0fHYRyj.js.br +0 -0
- package/dist/shell/assets/python-B0fHYRyj.js.gz +0 -0
- package/dist/shell/assets/razor-C48vGnF3.js.br +0 -0
- package/dist/shell/assets/razor-C48vGnF3.js.gz +0 -0
- package/dist/shell/assets/toyon-mono-bold-u3Ky0k2u.woff2 +0 -0
- package/dist/shell/assets/toyon-mono-regular-BC3CdGld.woff2 +0 -0
- package/dist/shell/assets/tsMode-2SIN2sKE.js.br +0 -0
- package/dist/shell/assets/tsMode-2SIN2sKE.js.gz +0 -0
- package/dist/shell/assets/xml-DPJN-pu8.js.br +0 -0
- package/dist/shell/assets/xml-DPJN-pu8.js.gz +0 -0
- package/dist/shell/assets/yaml-CDzmW7EM.js.br +0 -0
- package/dist/shell/assets/yaml-CDzmW7EM.js.gz +0 -0
package/dist/daemon.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ../daemon/src/index.ts
|
|
3
3
|
import { rmSync as rmSync10, writeFileSync as writeFileSync14 } from "fs";
|
|
4
4
|
import { homedir as homedir15 } from "os";
|
|
5
|
-
import { dirname as
|
|
5
|
+
import { dirname as dirname17, join as join37 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
8
|
// ../shared/src/protocol/events.ts
|
|
@@ -14,6 +14,7 @@ var TOYON_MCP_SERVER = "toyon";
|
|
|
14
14
|
var HANDOFF_TOOL = "handoff";
|
|
15
15
|
var WORKTREES_TOOL = "worktrees";
|
|
16
16
|
var OWN_TOOL = "own";
|
|
17
|
+
var STOP_TOOL = "stop";
|
|
17
18
|
function coalesce(entries) {
|
|
18
19
|
const out = [];
|
|
19
20
|
for (const entry of entries) {
|
|
@@ -6787,7 +6788,7 @@ ${text.slice(text.length - (CONTEXT_CHARS - HEAD_CHARS))}`;
|
|
|
6787
6788
|
}
|
|
6788
6789
|
|
|
6789
6790
|
// ../shared/src/protocol/limits.ts
|
|
6790
|
-
var PROTOCOL_VERSION =
|
|
6791
|
+
var PROTOCOL_VERSION = 151;
|
|
6791
6792
|
var PAGE_ERROR_MAX_CHARS = 2000;
|
|
6792
6793
|
var PAGE_TITLE_MAX_CHARS = 1000;
|
|
6793
6794
|
var FILE_MAX_CHARS = 5000000;
|
|
@@ -7216,6 +7217,8 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
7216
7217
|
object({ t: literal("continue"), worktreeId: id }),
|
|
7217
7218
|
object({ t: literal("reveal"), worktreeId: id, path: relPath.optional() }),
|
|
7218
7219
|
object({ t: literal("reveal-path"), path: relPath }),
|
|
7220
|
+
object({ t: literal("copy-file"), worktreeId: id, path: relPath }),
|
|
7221
|
+
object({ t: literal("copy-file-path"), path: relPath }),
|
|
7219
7222
|
object({ t: literal("reveal-attachment"), worktreeId: id, file: string2().min(1).max(120) }),
|
|
7220
7223
|
object({ t: literal("reveal-transcript"), worktreeId: id }),
|
|
7221
7224
|
object({ t: literal("stop-agent"), worktreeId: id }),
|
|
@@ -8844,7 +8847,7 @@ async function turnRemoteOff(ts, daemonPort, file, was) {
|
|
|
8844
8847
|
// ../daemon/package.json
|
|
8845
8848
|
var package_default = {
|
|
8846
8849
|
name: "@toyon/daemon",
|
|
8847
|
-
version: "0.
|
|
8850
|
+
version: "0.25.0",
|
|
8848
8851
|
private: true,
|
|
8849
8852
|
type: "module",
|
|
8850
8853
|
main: "src/index.ts",
|
|
@@ -12766,10 +12769,18 @@ function envLevel() {
|
|
|
12766
12769
|
}
|
|
12767
12770
|
var threshold = LEVELS[envLevel()];
|
|
12768
12771
|
var BUGS = [TypeError, RangeError, ReferenceError, SyntaxError];
|
|
12772
|
+
function details(e) {
|
|
12773
|
+
const d = e.data?.details;
|
|
12774
|
+
return typeof d === "string" && d ? `: ${d.split(`
|
|
12775
|
+
`)[0]}` : "";
|
|
12776
|
+
}
|
|
12769
12777
|
function fmtError(level, e) {
|
|
12770
|
-
const
|
|
12778
|
+
const head = `${e.name}: ${e.message}`;
|
|
12779
|
+
const line = head + details(e);
|
|
12771
12780
|
const whole = level === "error" || threshold <= LEVELS.debug || BUGS.some((c) => e instanceof c);
|
|
12772
|
-
|
|
12781
|
+
if (!whole || !e.stack)
|
|
12782
|
+
return line;
|
|
12783
|
+
return e.stack.startsWith(head) ? line + e.stack.slice(head.length) : e.stack;
|
|
12773
12784
|
}
|
|
12774
12785
|
function fmtExtra(level, extra) {
|
|
12775
12786
|
if (extra === undefined)
|
|
@@ -13657,6 +13668,13 @@ function isAuthRequired(e) {
|
|
|
13657
13668
|
return e instanceof RequestError && e.code === -32000;
|
|
13658
13669
|
}
|
|
13659
13670
|
|
|
13671
|
+
// ../daemon/src/agent/acp/errors.ts
|
|
13672
|
+
function errorWords(e) {
|
|
13673
|
+
const details = e instanceof RequestError ? e.data?.details : undefined;
|
|
13674
|
+
const message = typeof details === "string" && details ? details : e instanceof Error ? e.message : String(e);
|
|
13675
|
+
return message.replace(/^Internal error: /, "");
|
|
13676
|
+
}
|
|
13677
|
+
|
|
13660
13678
|
// ../daemon/src/agent/acp/options.ts
|
|
13661
13679
|
var OPTION_FIELDS = { model: "model", thought_level: "effort" };
|
|
13662
13680
|
var OPTION_CATEGORIES = Object.keys(OPTION_FIELDS);
|
|
@@ -13846,7 +13864,7 @@ class AgentReadiness {
|
|
|
13846
13864
|
}
|
|
13847
13865
|
if (init.agentCapabilities?.sessionCapabilities?.close) {
|
|
13848
13866
|
await link.conn.agent.request(methods.agent.session.close, { sessionId: s.sessionId }).catch((e) => {
|
|
13849
|
-
log.debug(tag, `session/close failed: ${
|
|
13867
|
+
log.debug(tag, `session/close failed: ${errorWords(e)}`);
|
|
13850
13868
|
});
|
|
13851
13869
|
}
|
|
13852
13870
|
} catch (e) {
|
|
@@ -13913,16 +13931,15 @@ class AgentReadiness {
|
|
|
13913
13931
|
} catch (e) {
|
|
13914
13932
|
if (e instanceof UserError)
|
|
13915
13933
|
throw e;
|
|
13916
|
-
throw new UserError(`${spec.name} could not be signed out: ${
|
|
13934
|
+
throw new UserError(`${spec.name} could not be signed out: ${errorWords(e)}`);
|
|
13917
13935
|
} finally {
|
|
13918
13936
|
await link.kill();
|
|
13919
13937
|
}
|
|
13920
13938
|
}
|
|
13921
13939
|
}
|
|
13922
13940
|
function firstLine(e) {
|
|
13923
|
-
const
|
|
13924
|
-
|
|
13925
|
-
return message.replace(/^Internal error: /, "").split(`
|
|
13941
|
+
const message = errorWords(e);
|
|
13942
|
+
return message.split(`
|
|
13926
13943
|
`)[0] ?? message;
|
|
13927
13944
|
}
|
|
13928
13945
|
function sameLimits(a, b) {
|
|
@@ -14929,7 +14946,7 @@ var BUILTIN_AGENTS = [
|
|
|
14929
14946
|
run: {
|
|
14930
14947
|
kind: "npm-bin",
|
|
14931
14948
|
pkg: "@toyon-dev/claude-agent-acp",
|
|
14932
|
-
version: "0.
|
|
14949
|
+
version: "0.106.0-toyon.1",
|
|
14933
14950
|
forkOf: "@agentclientprotocol/claude-agent-acp",
|
|
14934
14951
|
bin: "claude-agent-acp"
|
|
14935
14952
|
},
|
|
@@ -15124,8 +15141,14 @@ class AgentRegistry {
|
|
|
15124
15141
|
}
|
|
15125
15142
|
if (!this.packageName(spec))
|
|
15126
15143
|
return { reason: `no build for ${this.host.platform} ${this.host.arch}` };
|
|
15127
|
-
if (this.installed(spec))
|
|
15128
|
-
|
|
15144
|
+
if (this.installed(spec)) {
|
|
15145
|
+
const owed = boxed || this.installing.has(spec.id) ? undefined : this.installErrors.get(spec.id);
|
|
15146
|
+
const runs = owed ? this.installedVersion(spec) : null;
|
|
15147
|
+
if (!owed || !runs || runs === spec.run.version)
|
|
15148
|
+
return { reason: boxed };
|
|
15149
|
+
const behind = { runs, pinned: spec.run.version };
|
|
15150
|
+
return { reason: null, install: { failed: owed.why, ...owed.held ? { held: true } : {}, behind } };
|
|
15151
|
+
}
|
|
15129
15152
|
if (this.installing.has(spec.id))
|
|
15130
15153
|
return { reason: "installing" };
|
|
15131
15154
|
const failed = this.installErrors.get(spec.id);
|
|
@@ -15697,6 +15720,13 @@ function backgroundStart(input, output) {
|
|
|
15697
15720
|
const m = STARTED.exec(output);
|
|
15698
15721
|
return m ? { taskId: m[1], file: m[2] } : null;
|
|
15699
15722
|
}
|
|
15723
|
+
var STOP_TOOL2 = "TaskStop";
|
|
15724
|
+
function stoppedTask(name, input) {
|
|
15725
|
+
if (name !== STOP_TOOL2)
|
|
15726
|
+
return null;
|
|
15727
|
+
const id = input && typeof input === "object" ? input.task_id : undefined;
|
|
15728
|
+
return typeof id === "string" ? id : null;
|
|
15729
|
+
}
|
|
15700
15730
|
function spawnDetached(output) {
|
|
15701
15731
|
return output.includes("Async agent launched successfully");
|
|
15702
15732
|
}
|
|
@@ -15721,7 +15751,7 @@ function taskEnd(text, toolId) {
|
|
|
15721
15751
|
return { status, exit: exit === undefined ? null : Number(exit) };
|
|
15722
15752
|
}
|
|
15723
15753
|
var POLL_MS = 500;
|
|
15724
|
-
var
|
|
15754
|
+
var BACKGROUND_LIMIT_MS = 2 * 60 * 60000;
|
|
15725
15755
|
var GRACE_MS = 60000;
|
|
15726
15756
|
var READ_CHUNK = 1 << 20;
|
|
15727
15757
|
var CARRY = 4000;
|
|
@@ -15735,14 +15765,13 @@ class BackgroundTasks {
|
|
|
15735
15765
|
get size() {
|
|
15736
15766
|
return this.watched.size;
|
|
15737
15767
|
}
|
|
15738
|
-
start(toolId, start
|
|
15768
|
+
start(toolId, start) {
|
|
15739
15769
|
const logPath = sessionLogFor(start.file, this.deps.configDir);
|
|
15740
15770
|
if (!logPath)
|
|
15741
15771
|
return false;
|
|
15742
|
-
const timeout = input?.timeout;
|
|
15743
|
-
const ceiling = (typeof timeout === "number" ? timeout : DEFAULT_BASH_TIMEOUT_MS) + (this.deps.graceMs ?? GRACE_MS);
|
|
15744
15772
|
const w = {
|
|
15745
15773
|
toolId,
|
|
15774
|
+
taskId: start.taskId,
|
|
15746
15775
|
file: start.file,
|
|
15747
15776
|
log: logPath,
|
|
15748
15777
|
read: 0,
|
|
@@ -15751,13 +15780,20 @@ class BackgroundTasks {
|
|
|
15751
15780
|
decoder: new TextDecoder,
|
|
15752
15781
|
text: "",
|
|
15753
15782
|
truncated: false,
|
|
15754
|
-
ceiling: Date.now() +
|
|
15783
|
+
ceiling: Date.now() + (this.deps.waitMs ?? BACKGROUND_LIMIT_MS + GRACE_MS),
|
|
15755
15784
|
timer: null
|
|
15756
15785
|
};
|
|
15757
15786
|
this.watched.set(toolId, w);
|
|
15758
15787
|
this.schedule(w);
|
|
15759
15788
|
return true;
|
|
15760
15789
|
}
|
|
15790
|
+
stop(taskId) {
|
|
15791
|
+
for (const w of [...this.watched.values()]) {
|
|
15792
|
+
if (w.taskId !== taskId)
|
|
15793
|
+
continue;
|
|
15794
|
+
fireAndForget("background", this.finish(w, null, "the agent stopped it", true), `ending ${w.toolId}`);
|
|
15795
|
+
}
|
|
15796
|
+
}
|
|
15761
15797
|
endAll(reason) {
|
|
15762
15798
|
for (const w of [...this.watched.values()]) {
|
|
15763
15799
|
fireAndForget("background", this.finish(w, null, reason, true), `ending ${w.toolId}`);
|
|
@@ -16080,7 +16116,7 @@ function abandoned(memos, parent, opening = false) {
|
|
|
16080
16116
|
}
|
|
16081
16117
|
return out;
|
|
16082
16118
|
}
|
|
16083
|
-
function mapUpdate(update, memos, tag, sink, background, wakeup) {
|
|
16119
|
+
function mapUpdate(update, memos, tag, sink, background, wakeup, stopped) {
|
|
16084
16120
|
switch (update.sessionUpdate) {
|
|
16085
16121
|
case "agent_message_chunk": {
|
|
16086
16122
|
const { parentToolId } = spawnOf(update._meta);
|
|
@@ -16127,7 +16163,7 @@ function mapUpdate(update, memos, tag, sink, background, wakeup) {
|
|
|
16127
16163
|
...spawn
|
|
16128
16164
|
});
|
|
16129
16165
|
if (update.status === "completed" || update.status === "failed")
|
|
16130
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink, background, wakeup));
|
|
16166
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background, wakeup, stopped));
|
|
16131
16167
|
return out;
|
|
16132
16168
|
}
|
|
16133
16169
|
case "tool_call_update": {
|
|
@@ -16184,7 +16220,7 @@ function mapUpdate(update, memos, tag, sink, background, wakeup) {
|
|
|
16184
16220
|
if (update.rawOutput !== undefined)
|
|
16185
16221
|
memo.rawOutput = update.rawOutput;
|
|
16186
16222
|
if ((update.status === "completed" || update.status === "failed") && !memo.ended) {
|
|
16187
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink, background, wakeup));
|
|
16223
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background, wakeup, stopped));
|
|
16188
16224
|
}
|
|
16189
16225
|
return out;
|
|
16190
16226
|
}
|
|
@@ -16215,14 +16251,19 @@ function textOf(content, tag, what) {
|
|
|
16215
16251
|
log.debug(tag, `acp: dropping non-text ${what} block (${content.type})`);
|
|
16216
16252
|
return null;
|
|
16217
16253
|
}
|
|
16218
|
-
function endOf(toolId, memo, status, sink, background, wakeup) {
|
|
16254
|
+
function endOf(toolId, memo, status, sink, background, wakeup, stopped) {
|
|
16219
16255
|
memo.ended = true;
|
|
16220
16256
|
const output = summarizeToolOutput(memo.content, memo.rawOutput);
|
|
16221
16257
|
if (status === "completed" && memo.kind === "execute" && background) {
|
|
16222
16258
|
const start = backgroundStart(memo.input, output);
|
|
16223
|
-
if (start && background(toolId, start
|
|
16259
|
+
if (start && background(toolId, start))
|
|
16224
16260
|
return { type: "tool-update", toolId, background: true };
|
|
16225
16261
|
}
|
|
16262
|
+
if (status === "completed" && stopped) {
|
|
16263
|
+
const taskId = stoppedTask(memo.name || memo.title, memo.input);
|
|
16264
|
+
if (taskId !== null)
|
|
16265
|
+
stopped(taskId);
|
|
16266
|
+
}
|
|
16226
16267
|
if (status === "completed" && wakeup) {
|
|
16227
16268
|
const name = memo.name || memo.title;
|
|
16228
16269
|
const until = wakeupAt(name, memo.input);
|
|
@@ -16477,6 +16518,7 @@ var SYSTEM_APPEND = [
|
|
|
16477
16518
|
"`@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.",
|
|
16478
16519
|
"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.",
|
|
16479
16520
|
"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.",
|
|
16521
|
+
"To stop the turn of a worktree made from a piece you handed off, when the user drops or pauses that work, call the `stop` tool when you have one, with the reason: it interrupts that turn and nothing else, and that chat says who stopped it and why. Stopping any other worktree, and archiving one, is the user's to do in Toyon; say so rather than looking for another way to reach its agent.",
|
|
16480
16522
|
"To run a command this worktree still reaches on main here, call the `own` tool when you have one, before you edit what that command runs on; Toyon would switch it on the edit anyway, and calling first means the server is up when the edit lands.",
|
|
16481
16523
|
`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", "api": {"cmd": "uv run uvicorn app.main:app --port $PORT", "setup": ["uv sync"], "remotes": {"stage": "https://api.stage.example.com"}}}, "check": "bun run check"}: 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, and check (optional) runs after each of your turns and must pass before the work can land. A run command may take the object form: cmd is the command; a command whose folder Toyon can place in the tree (or whose paths are listed) runs once in the main checkout and every worktree's page reaches it at its <NAME>_URL until the worktree changes those files, when it starts its own; "from": "copy" makes every worktree run its own and "from": "trunk" makes main run it whatever the paths say, with no change flipping a command that names no folder; its setup and teardown run in a worktree before it first runs that command there and when the worktree goes; remotes names addresses the page may point it at instead. The paragraph attached to each message says which commands this worktree reaches on main. 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.`,
|
|
16482
16524
|
"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.",
|
|
@@ -16641,16 +16683,22 @@ function handoffActedPrompt(pieces, outcomes, by = "plan", note) {
|
|
|
16641
16683
|
const acted = parts.length ? `: ${parts.join("; ")}` : "";
|
|
16642
16684
|
const siblings = started.length || queued.length || sent.length ? " The siblings read the list at its path in this worktree; never do their pieces yourself." : "";
|
|
16643
16685
|
const said = note ? ` The person added: ${note}` : "";
|
|
16644
|
-
return `The person answered the handoff card, and Toyon acted on the pieces you handed over${acted}. ${own}${
|
|
16686
|
+
return `The person answered the handoff card, and Toyon acted on the pieces you handed over${acted}. ${own}${siblings}${said}`;
|
|
16645
16687
|
}
|
|
16646
16688
|
return `Toyon acted on the pieces you handed over with the plan's approval: ${parts.join("; ")}. ${own} The siblings read the plan at its path in this worktree; never do their pieces yourself.`;
|
|
16647
16689
|
}
|
|
16648
16690
|
function handoffDeclinedPrompt(pieces, note) {
|
|
16649
|
-
|
|
16650
|
-
return `The person answered the handoff card with not now: nothing on the list you handed over (${titles}) was started, and none of it is yours to do from that list. They added: ${note}
|
|
16691
|
+
return `${handoffDeclinedContext(pieces)} They added: ${note}
|
|
16651
16692
|
|
|
16652
16693
|
Read that as the word on what to change. Hand over a new list with the handoff tool if a split still fits, or go on without one.`;
|
|
16653
16694
|
}
|
|
16695
|
+
function handoffDeclinedContext(pieces, failed) {
|
|
16696
|
+
const titles = pieces.map((p) => `"${p.title}"`).join(", ");
|
|
16697
|
+
if (failed === undefined) {
|
|
16698
|
+
return `The person answered the handoff card with not now: nothing on the list you handed over (${titles}) was started, and none of it is yours to do from that list.`;
|
|
16699
|
+
}
|
|
16700
|
+
return `Toyon could not finish acting on the list you handed over (${titles}): ${failed.replace(/\.$/, "")}. A piece may not have started; say so, and hand it over again if the split still fits.`;
|
|
16701
|
+
}
|
|
16654
16702
|
function sentFromLine(path, by) {
|
|
16655
16703
|
const who = by ? `, where ${by} was working` : "";
|
|
16656
16704
|
return `This message was sent from a terminal in ${path} with \`toyon send\`${who}.`;
|
|
@@ -16675,29 +16723,30 @@ function pieceAskPrompt(text, project) {
|
|
|
16675
16723
|
const where = project ? `in a new worktree of ${project.name} (${project.path}), another project open in Toyon. Propose it with the \`handoff\` tool: one piece, \`where\` set to \`worktree\` and \`project\` to "${project.path}", with a title (${NAME_SHAPE}) 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 \`handoff\` tool: one piece, \`where\` set to \`worktree\`, with a title (${NAME_SHAPE}) and a plan its agent builds from, with what is needed and why and the paths here that depend on it.`;
|
|
16676
16724
|
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.";
|
|
16677
16725
|
}
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
}
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
if (same) {
|
|
16687
|
-
return `The sibling worktree "${title}", a piece of the work planned here, has landed on this project's main${where}; anything here that waited on it can take the real change now, and a later piece that waited for it has started.`;
|
|
16688
|
-
}
|
|
16689
|
-
return `The work this worktree handed off to ${project}, "${title}", has landed there${where}; anything here that waited on it, such as a pinned version or a hand-patched copy, can take the real change now.`;
|
|
16726
|
+
var chatAt = (transcript) => `Its chat is at ${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.`;
|
|
16727
|
+
function pieceLandedContext(landed, chat) {
|
|
16728
|
+
const { title } = landed;
|
|
16729
|
+
const where = landed.url ? ` (${landed.url})` : "";
|
|
16730
|
+
const what = landed.subjects?.length ? ` What landed: ${landed.subjects.map((s) => `"${s}"`).join("; ")}.` : "";
|
|
16731
|
+
const read = chat ? ` ${chatAt(chat)} Read its agent's last replies there when what it checked or left undone matters here.` : "";
|
|
16732
|
+
const said = landed.awaited ? `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.` : landed.again ? `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.` : landed.sent ? `The worktree this one sent a piece to, "${title}"${landed.same ? "" : ` in ${landed.repoName}`}, has landed on ${landed.same ? "this project's" : "that project's"} main${where}; the landing is that worktree's own, so check that it holds the piece before anything here relies on it.` : landed.same ? `The sibling worktree "${title}", a piece of the work planned here, has landed on this project's main${where}; anything here that waited on it can take the real change now, and a later piece that waited for it has started.` : `The work this worktree handed off to ${landed.repoName}, "${title}", has landed there${where}; anything here that waited on it, such as a pinned version or a hand-patched copy, can take the real change now.`;
|
|
16733
|
+
return said + what + read;
|
|
16690
16734
|
}
|
|
16691
16735
|
function pieceWakePrompt(landed) {
|
|
16692
16736
|
const quoted = (xs) => xs.map((x) => `"${x}"`).join(", ");
|
|
16693
16737
|
const sentences = landed.map((l) => {
|
|
16694
16738
|
const where = l.url ? ` (${l.url})` : "";
|
|
16695
16739
|
const main = l.project ? `main in the project ${l.project}` : "this project's main";
|
|
16740
|
+
if (l.handed)
|
|
16741
|
+
return `The work this worktree handed off, "${l.title}" (${l.branch}), has landed on ${main}${where}.`;
|
|
16742
|
+
if (l.sent)
|
|
16743
|
+
return `The worktree this one sent a piece to, "${l.title}" (${l.branch}), has landed on ${main}${where}; check that the landing holds the piece.`;
|
|
16696
16744
|
const own = l.pieces.length === 0 ? "your own piece" : `your own ${l.pieces.length === 1 ? "piece" : "pieces"} ${quoted(l.pieces)}`;
|
|
16697
16745
|
return `The branch ${own} waited for, "${l.title}" (${l.branch}), has landed on ${main}${where}.`;
|
|
16698
16746
|
});
|
|
16699
|
-
const
|
|
16700
|
-
const
|
|
16747
|
+
const waited = landed.filter((l) => !l.handed && !l.sent);
|
|
16748
|
+
const pieces = [...new Set(waited.flatMap((l) => l.pieces))];
|
|
16749
|
+
const build = waited.length === 0 ? `Nothing here was set to wait for ${landed.length === 1 ? "it" : "them"}. If something here depends on what landed, such as a pinned version or a copy patched by hand, bring it in line now, and only that; otherwise change nothing. Then say in a line or two what landed and what you did with it.` : pieces.length === 0 ? "Do the piece that waited here now, and only that." : `Do ${quoted(pieces)} here now, and only ${pieces.length === 1 ? "that" : "those"}.`;
|
|
16701
16750
|
const titles = quoted(landed.map((l) => l.title));
|
|
16702
16751
|
const here = landed.some((l) => !l.project);
|
|
16703
16752
|
const commits = `What ${titles} landed is readable on main now${here ? ", and Toyon rebases onto main when this lands" : ""}.`;
|
|
@@ -16718,7 +16767,7 @@ function archivedFate(a) {
|
|
|
16718
16767
|
return "without landing, and git kept nothing of it";
|
|
16719
16768
|
}
|
|
16720
16769
|
function siblingParagraph(s) {
|
|
16721
|
-
const chat =
|
|
16770
|
+
const chat = chatAt(s.transcript);
|
|
16722
16771
|
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}`;
|
|
16723
16772
|
}
|
|
16724
16773
|
function worktreeContext(text, siblings) {
|
|
@@ -17074,16 +17123,22 @@ async function askRun(ctx, a, opened) {
|
|
|
17074
17123
|
if (a.quick) {
|
|
17075
17124
|
const model = readOptions(s.configOptions).get("model");
|
|
17076
17125
|
const quick = model && quickModelFor(a.spec, model.ids, model.current);
|
|
17077
|
-
|
|
17078
|
-
|
|
17126
|
+
let onQuick = !!quick && model?.current === quick;
|
|
17127
|
+
if (model && quick && !onQuick) {
|
|
17128
|
+
try {
|
|
17079
17129
|
await ctx.request(methods.agent.session.setConfigOption, {
|
|
17080
17130
|
sessionId: s.sessionId,
|
|
17081
17131
|
configId: model.id,
|
|
17082
17132
|
value: quick
|
|
17083
17133
|
});
|
|
17134
|
+
onQuick = true;
|
|
17135
|
+
} catch (e) {
|
|
17136
|
+
log.warn(a.tag, `ask: ${a.spec.id} would not move to its quick model ${quick}`, e);
|
|
17084
17137
|
}
|
|
17085
|
-
} else if (
|
|
17086
|
-
|
|
17138
|
+
} else if (model && !quick)
|
|
17139
|
+
noteNotOffered(a, model.ids);
|
|
17140
|
+
if (!onQuick && a.quick === "require") {
|
|
17141
|
+
log.debug(a.tag, `ask: ${a.spec.id} has no quick model to run on; not asked`);
|
|
17087
17142
|
return null;
|
|
17088
17143
|
}
|
|
17089
17144
|
}
|
|
@@ -17121,9 +17176,20 @@ ${a.prompt}`;
|
|
|
17121
17176
|
fireAndForget(a.tag, cleanUp(ctx, a, s.sessionId), "ask: clean up side session");
|
|
17122
17177
|
}
|
|
17123
17178
|
}
|
|
17179
|
+
var notOffered = new Set;
|
|
17180
|
+
function noteNotOffered(a, offered) {
|
|
17181
|
+
const named = a.spec.quickModel;
|
|
17182
|
+
if (typeof named !== "string")
|
|
17183
|
+
return;
|
|
17184
|
+
const key = `${a.spec.id} ${named}`;
|
|
17185
|
+
if (notOffered.has(key))
|
|
17186
|
+
return;
|
|
17187
|
+
notOffered.add(key);
|
|
17188
|
+
log.warn(a.tag, `ask: ${a.spec.id} does not offer its quick model ${named}; it offers ${offered.join(", ")}`);
|
|
17189
|
+
}
|
|
17124
17190
|
async function cleanUp(ctx, a, sessionId) {
|
|
17125
17191
|
const quietly = (what) => (e) => {
|
|
17126
|
-
log.debug(a.tag, `ask: session/${what} failed: ${
|
|
17192
|
+
log.debug(a.tag, `ask: session/${what} failed: ${errorWords(e)}`);
|
|
17127
17193
|
};
|
|
17128
17194
|
if (a.caps.close)
|
|
17129
17195
|
await ctx.request(methods.agent.session.close, { sessionId }).catch(quietly("close"));
|
|
@@ -17300,7 +17366,7 @@ function steerOutcome(value) {
|
|
|
17300
17366
|
// ../daemon/src/agent/acp/wire.ts
|
|
17301
17367
|
var FLAG_NAMES = ["noStartSignal", "cutCallUnreported", "backgroundFinish", "modelIdOffList"];
|
|
17302
17368
|
var CLAUDE = {
|
|
17303
|
-
at: "0.
|
|
17369
|
+
at: "0.106.0-toyon.1",
|
|
17304
17370
|
gaps: {
|
|
17305
17371
|
partialInput: {
|
|
17306
17372
|
gap: "A call's input arrives one top-level field at a time, as `tool_call_update`s carrying `rawInput` and " + "nothing else, and nothing marks a refine partial or the last one whole. The whole input of a read " + "or a plain command arrives the same way.",
|
|
@@ -17323,12 +17389,12 @@ var CLAUDE = {
|
|
|
17323
17389
|
steerPriority: {
|
|
17324
17390
|
gap: "The adapter picks the SDK priority of a steered message: `now`, which aborts the step the CLI is on " + "(since CLI 2.1.286 a foreground tool moves to the background instead), and `later` only while a " + "card is open, which waits for the generation after the tool result. Toyon cannot ask for `next`, " + "between steps, which is what a message sent mid-turn means.",
|
|
17325
17391
|
here: 'session.ts asks for `priority: "next"` under `_meta.steering` on every steer, which the fork honours ' + "(upstream ignores the key and keeps `now`), and sends a card's follow-up (an image, a note, the " + "edited plan) before the card's reply resolves so it lands on `later`. No flag: the ask and the " + "ordering are harmless either way.",
|
|
17326
|
-
upstream: "Asked upstream twice, agentclientprotocol/claude-agent-acp#1175 and #1350 (the better shape, " + "`_meta.steering.priority`), both unreviewed; still `now` at 0.
|
|
17392
|
+
upstream: "Asked upstream twice, agentclientprotocol/claude-agent-acp#1175 and #1350 (the better shape, " + "`_meta.steering.priority`), both unreviewed; still `now` at 0.106.0. The fork pin carries #1350 " + "(`pr/1350-steer-priority`) until one lands; back #1350 rather than open a third. The steering " + "extension itself says nothing about the running step.",
|
|
17327
17393
|
open: false
|
|
17328
17394
|
},
|
|
17329
17395
|
backgroundFinish: {
|
|
17330
17396
|
gap: "A command sent to the background returns at once with a task id and an output path, and the wire " + "never mentions it again.",
|
|
17331
|
-
here: "background.ts tails the output file into the row and reads the finish off Claude Code's own session " + "log, on four strings toyon does not own. Flag: the sink mapUpdate keeps a background row open through.",
|
|
17397
|
+
here: "background.ts tails the output file into the row and reads the finish off Claude Code's own session " + "log, on four strings toyon does not own. A task the agent stops gets no notice there, and its row " + "ends on the TaskStop call naming it. Flag: the sink mapUpdate keeps a background row open through.",
|
|
17332
17398
|
upstream: "Shipped: `async_task_spawned`, `async_task_progress`, `async_task_state_update` and " + "`_session/async_task/stop`, but only to a client declaring `_meta.jetbrains.air.capabilities` with " + "`asyncTasks` at initialize; no AIR-only key reaches any other client. Ask for a neutral capability, " + "or declare that meta.",
|
|
17333
17399
|
open: true
|
|
17334
17400
|
},
|
|
@@ -17363,9 +17429,9 @@ var CLAUDE = {
|
|
|
17363
17429
|
open: true
|
|
17364
17430
|
},
|
|
17365
17431
|
authStatusMeta: {
|
|
17366
|
-
gap: "Which credential the agent runs on arrives as `_auth/status_update`, advertised under " + "`agentCapabilities._meta.authStatus`, with no request method.",
|
|
17367
|
-
here: "authstatus.ts. No flag.",
|
|
17368
|
-
upstream: "Interim extension, on both builtin adapters
|
|
17432
|
+
gap: "Which credential the agent runs on arrives as `_auth/status_update`, advertised under " + "`agentCapabilities._meta.authStatus`, with no request method. Since 0.104.0 the same identity is " + "also pushed as ACP's own `auth/status_update` and answered on `auth/status` (`auth.status`).",
|
|
17433
|
+
here: "authstatus.ts reads the private one. The daemon's SDK has no `auth/status_update`, so that push " + "meets no handler and is dropped. No flag.",
|
|
17434
|
+
upstream: "Interim extension, on both builtin adapters. Claude's first-class method has shipped; reading it " + "comes with an SDK that carries it.",
|
|
17369
17435
|
open: true
|
|
17370
17436
|
},
|
|
17371
17437
|
steeringMeta: {
|
|
@@ -17381,7 +17447,7 @@ var CLAUDE = {
|
|
|
17381
17447
|
open: true
|
|
17382
17448
|
},
|
|
17383
17449
|
droppedUpdates: {
|
|
17384
|
-
gap: "Updates toyon gets and does not read: `plan`
|
|
17450
|
+
gap: "Updates toyon gets and does not read: `plan` (the task list; `plan_update` and `plan_removed` go " + "to a client declaring `clientCapabilities.plan`), `compaction_update`, " + "`current_mode_update` beyond the plan check, `promptSuggestions` under `session_info_update._meta`, " + "and the title the adapter generates for the session. `notice` is withheld because toyon does not " + "declare `clientCapabilities.session.notices`.",
|
|
17385
17451
|
here: "map.ts drops them at its default arm; session.ts reads the mode update for plan only. No flag.",
|
|
17386
17452
|
upstream: "Shipped. Each is daemon work to read, not an ask.",
|
|
17387
17453
|
open: true
|
|
@@ -17970,16 +18036,19 @@ class AcpSession {
|
|
|
17970
18036
|
this.steered.splice(at, 1);
|
|
17971
18037
|
return at >= 0;
|
|
17972
18038
|
}
|
|
17973
|
-
stop() {
|
|
18039
|
+
stop(by) {
|
|
17974
18040
|
this.cancelAsks();
|
|
17975
18041
|
if (!this.running) {
|
|
17976
18042
|
if (this.own && this.live) {
|
|
17977
18043
|
const { conn, sessionId } = this.live;
|
|
17978
18044
|
fireAndForget(this.d.worktreeId, conn.ctx.notify(methods.agent.session.cancel, { sessionId }), "cancel");
|
|
17979
|
-
this.endOwn("interrupted");
|
|
18045
|
+
this.endOwn("interrupted", by);
|
|
18046
|
+
return true;
|
|
17980
18047
|
}
|
|
17981
|
-
return;
|
|
18048
|
+
return false;
|
|
17982
18049
|
}
|
|
18050
|
+
if (by && this.turn && !this.turn.stopping)
|
|
18051
|
+
this.turn.by = by;
|
|
17983
18052
|
this.setStopping(true);
|
|
17984
18053
|
if (this.steered.length > 0) {
|
|
17985
18054
|
for (const item of this.steered)
|
|
@@ -17992,6 +18061,7 @@ class AcpSession {
|
|
|
17992
18061
|
if (live) {
|
|
17993
18062
|
fireAndForget(this.d.worktreeId, live.conn.ctx.notify(methods.agent.session.cancel, { sessionId: live.sessionId }), "cancel");
|
|
17994
18063
|
}
|
|
18064
|
+
return true;
|
|
17995
18065
|
}
|
|
17996
18066
|
async close() {
|
|
17997
18067
|
this.stopped = true;
|
|
@@ -18077,11 +18147,11 @@ class AcpSession {
|
|
|
18077
18147
|
if (!turn?.open)
|
|
18078
18148
|
return;
|
|
18079
18149
|
turn.open = false;
|
|
18080
|
-
this.turnOver(stopReason);
|
|
18150
|
+
this.turnOver(stopReason, stopReason === "interrupted" ? turn.by : undefined);
|
|
18081
18151
|
this.setStopping(false);
|
|
18082
18152
|
}
|
|
18083
|
-
turnOver(stopReason) {
|
|
18084
|
-
this.emit({ type: "turn-end", stopReason, ts: Date.now() });
|
|
18153
|
+
turnOver(stopReason, by) {
|
|
18154
|
+
this.emit({ type: "turn-end", stopReason, ...by ? { by } : {}, ts: Date.now() });
|
|
18085
18155
|
this.showHeld();
|
|
18086
18156
|
}
|
|
18087
18157
|
async ask(system, prompt, opts = {}) {
|
|
@@ -18144,7 +18214,7 @@ class AcpSession {
|
|
|
18144
18214
|
...apiKey ? { _meta: { "api-key": { apiKey } } } : {}
|
|
18145
18215
|
});
|
|
18146
18216
|
} catch (e) {
|
|
18147
|
-
throw new UserError(`${conn.spec.name} login failed: ${
|
|
18217
|
+
throw new UserError(`${conn.spec.name} login failed: ${errorWords(e)}`);
|
|
18148
18218
|
}
|
|
18149
18219
|
this.loggedIn();
|
|
18150
18220
|
return { kind: "done" };
|
|
@@ -18191,12 +18261,12 @@ class AcpSession {
|
|
|
18191
18261
|
rejectedCredential(e) {
|
|
18192
18262
|
if (!this.conn?.authMethods.length)
|
|
18193
18263
|
return false;
|
|
18194
|
-
return REJECTED_CREDENTIAL_RE.test(
|
|
18264
|
+
return REJECTED_CREDENTIAL_RE.test(errorWords(e));
|
|
18195
18265
|
}
|
|
18196
18266
|
describe(e) {
|
|
18197
18267
|
if (isAuthRequired(e))
|
|
18198
18268
|
return this.conn?.spec.loginHint ?? this.d.spec().loginHint;
|
|
18199
|
-
const message = (e
|
|
18269
|
+
const message = errorWords(e);
|
|
18200
18270
|
return /connection closed/i.test(message) ? this.conn?.link.exitInfo() ?? message : message;
|
|
18201
18271
|
}
|
|
18202
18272
|
async record({ text, attachments, handed }) {
|
|
@@ -18261,10 +18331,10 @@ class AcpSession {
|
|
|
18261
18331
|
this.d.preview?.(),
|
|
18262
18332
|
this.d.tier?.(),
|
|
18263
18333
|
this.d.pageErrors?.(),
|
|
18264
|
-
...this.
|
|
18334
|
+
...this.heardSince()
|
|
18265
18335
|
]);
|
|
18266
18336
|
}
|
|
18267
|
-
|
|
18337
|
+
heardSince() {
|
|
18268
18338
|
const entries = this.log.entries;
|
|
18269
18339
|
let i = entries.length - 1;
|
|
18270
18340
|
while (i >= 0 && !isMessage(entries[i].event))
|
|
@@ -18274,12 +18344,31 @@ class AcpSession {
|
|
|
18274
18344
|
const e = entries[i].event;
|
|
18275
18345
|
if (isMessage(e))
|
|
18276
18346
|
break;
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18347
|
+
const said = e.type === "piece-landed" ? pieceLandedContext(e, this.d.chatOf?.(e.worktreeId)) : this.cardAnswered(e);
|
|
18348
|
+
if (said)
|
|
18349
|
+
out.unshift(said);
|
|
18280
18350
|
}
|
|
18281
18351
|
return out;
|
|
18282
18352
|
}
|
|
18353
|
+
cardAnswered(e) {
|
|
18354
|
+
if (e.type !== "handoff" && e.type !== "handoff-declined")
|
|
18355
|
+
return;
|
|
18356
|
+
const list = this.proposed(e.id);
|
|
18357
|
+
if (!list || this.mcpTools === false)
|
|
18358
|
+
return;
|
|
18359
|
+
if (e.type === "handoff-declined" && e.outcome === "failed")
|
|
18360
|
+
return handoffDeclinedContext(list.pieces, e.reason);
|
|
18361
|
+
const withPlan = list.held && !this.log.entries.some(({ event }) => event.type === "handoff-shown" && event.id === e.id);
|
|
18362
|
+
if (withPlan)
|
|
18363
|
+
return;
|
|
18364
|
+
if (e.type === "handoff-declined") {
|
|
18365
|
+
return e.note || e.reason ? undefined : handoffDeclinedContext(list.pieces);
|
|
18366
|
+
}
|
|
18367
|
+
if (e.outcomes.some((o) => o.fate === "here" && !o.after))
|
|
18368
|
+
return;
|
|
18369
|
+
const go = this.log.entries.findLast(({ event }) => event.type === "handoff-go" && event.id === e.id)?.event;
|
|
18370
|
+
return handoffActedPrompt(list.pieces, e.outcomes, "card", go?.type === "handoff-go" ? go.note : undefined);
|
|
18371
|
+
}
|
|
18283
18372
|
ensureConn(why) {
|
|
18284
18373
|
if (this.conn)
|
|
18285
18374
|
return Promise.resolve(this.conn);
|
|
@@ -18546,12 +18635,12 @@ class AcpSession {
|
|
|
18546
18635
|
}
|
|
18547
18636
|
}
|
|
18548
18637
|
}
|
|
18549
|
-
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"), wireFlags(live.conn.spec.id).backgroundFinish ? (toolId, start
|
|
18550
|
-
const taken = this.tasks.start(toolId, start
|
|
18638
|
+
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"), wireFlags(live.conn.spec.id).backgroundFinish ? (toolId, start) => {
|
|
18639
|
+
const taken = this.tasks.start(toolId, start);
|
|
18551
18640
|
if (taken)
|
|
18552
18641
|
this.holdingChanged();
|
|
18553
18642
|
return taken;
|
|
18554
|
-
} : undefined, (toolId, until) => until === null ? this.wakeups.stop() : this.wakeups.start(toolId, until));
|
|
18643
|
+
} : undefined, (toolId, until) => until === null ? this.wakeups.stop() : this.wakeups.start(toolId, until), (taskId) => this.tasks.stop(taskId));
|
|
18555
18644
|
this.forward(live, events);
|
|
18556
18645
|
this.holdInput(live, params.update);
|
|
18557
18646
|
}
|
|
@@ -18798,13 +18887,13 @@ class AcpSession {
|
|
|
18798
18887
|
t.unref?.();
|
|
18799
18888
|
this.own.settle = t;
|
|
18800
18889
|
}
|
|
18801
|
-
endOwn(stopReason) {
|
|
18890
|
+
endOwn(stopReason, by) {
|
|
18802
18891
|
if (!this.own)
|
|
18803
18892
|
return;
|
|
18804
18893
|
if (this.own.settle)
|
|
18805
18894
|
clearTimeout(this.own.settle);
|
|
18806
18895
|
this.own = null;
|
|
18807
|
-
this.turnOver(stopReason);
|
|
18896
|
+
this.turnOver(stopReason, by);
|
|
18808
18897
|
this.syncStatus();
|
|
18809
18898
|
this.maybeArmReaper();
|
|
18810
18899
|
}
|
|
@@ -21128,30 +21217,57 @@ function startForward(opts) {
|
|
|
21128
21217
|
var MAC_PRESSURE_CRITICAL = 4;
|
|
21129
21218
|
var AVAILABLE_FLOOR = 0.15;
|
|
21130
21219
|
var PAGING_FLOOR_BPS = 16 * 2 ** 20;
|
|
21220
|
+
var HARD_PAGING_BPS = 4 * PAGING_FLOOR_BPS;
|
|
21131
21221
|
var paging = [];
|
|
21222
|
+
var readers = { darwin, linux };
|
|
21132
21223
|
var unsupportedLogged = false;
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
if (
|
|
21136
|
-
unsupportedLogged
|
|
21137
|
-
|
|
21224
|
+
var failures = 0;
|
|
21225
|
+
async function memoryTight(read = readers[process.platform]) {
|
|
21226
|
+
if (!read) {
|
|
21227
|
+
if (!unsupportedLogged) {
|
|
21228
|
+
unsupportedLogged = true;
|
|
21229
|
+
log.warn("memory", `no memory pressure reading on ${process.platform}; nothing sleeps for memory`);
|
|
21230
|
+
}
|
|
21231
|
+
return null;
|
|
21138
21232
|
}
|
|
21139
|
-
|
|
21233
|
+
const reading = await read();
|
|
21234
|
+
if ("failed" in reading) {
|
|
21235
|
+
if (failures++ === 0) {
|
|
21236
|
+
log.warn("memory", `memory reading failed: ${reading.failed}; nothing sleeps for memory until it reads again`);
|
|
21237
|
+
}
|
|
21238
|
+
return null;
|
|
21239
|
+
}
|
|
21240
|
+
if (failures > 0) {
|
|
21241
|
+
log.info("memory", `memory reads again after ${failures} failed ${failures === 1 ? "check" : "checks"}`);
|
|
21242
|
+
failures = 0;
|
|
21243
|
+
}
|
|
21244
|
+
return reading;
|
|
21140
21245
|
}
|
|
21246
|
+
var SYSCTL_NAMES = [
|
|
21247
|
+
"kern.memorystatus_vm_pressure_level",
|
|
21248
|
+
"kern.memorystatus_level",
|
|
21249
|
+
"vm.compressor.pages_compressed",
|
|
21250
|
+
"vm.compressor.pages_compressed_limit",
|
|
21251
|
+
"vm.compressor.segment.total",
|
|
21252
|
+
"vm.compressor.segment.limit"
|
|
21253
|
+
];
|
|
21141
21254
|
async function darwin() {
|
|
21142
|
-
const [r, v] = await Promise.all([
|
|
21143
|
-
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
if (!r.ok)
|
|
21147
|
-
return null;
|
|
21148
|
-
const [level, available] = r.out.split(/\s+/).map(Number);
|
|
21149
|
-
if (level === undefined || available === undefined || Number.isNaN(level) || Number.isNaN(available))
|
|
21150
|
-
return null;
|
|
21255
|
+
const [r, v] = await Promise.all([run("sysctl", ["-n", ...SYSCTL_NAMES], "/"), run("vm_stat", [], "/")]);
|
|
21256
|
+
const read = parseSysctl(r.out);
|
|
21257
|
+
if (!read)
|
|
21258
|
+
return { failed: `sysctl: ${r.err || "nothing to read in what it printed"}` };
|
|
21151
21259
|
const sample = v.ok ? parseVmStat(v.out, Date.now()) : null;
|
|
21152
21260
|
if (sample)
|
|
21153
21261
|
paging = [...paging, sample].slice(-3);
|
|
21154
|
-
return darwinSignal(level, available, sustainedPaging(paging));
|
|
21262
|
+
return darwinSignal(read.level, read.available, sustainedPaging(paging), read.compressor);
|
|
21263
|
+
}
|
|
21264
|
+
function parseSysctl(text) {
|
|
21265
|
+
const [level, available, pages, pageLimit, segments, segmentLimit] = text.split(`
|
|
21266
|
+
`).map((line) => /^\d+$/.test(line.trim()) ? Number(line) : undefined);
|
|
21267
|
+
if (level === undefined || available === undefined)
|
|
21268
|
+
return null;
|
|
21269
|
+
const known = pages !== undefined && segments !== undefined && !!pageLimit && !!segmentLimit;
|
|
21270
|
+
return { level, available, compressor: known ? Math.max(pages / pageLimit, segments / segmentLimit) : null };
|
|
21155
21271
|
}
|
|
21156
21272
|
function parseVmStat(text, at) {
|
|
21157
21273
|
const pageSize = text.match(/page size of (\d+) bytes/);
|
|
@@ -21175,20 +21291,24 @@ function sustainedPaging(samples) {
|
|
|
21175
21291
|
}
|
|
21176
21292
|
return Math.min(...rates);
|
|
21177
21293
|
}
|
|
21178
|
-
function darwinSignal(level, available, pagingBps = null) {
|
|
21294
|
+
function darwinSignal(level, available, pagingBps = null, compressor = null) {
|
|
21179
21295
|
const pressure = level === 1 ? "normal" : level === 2 ? "warn" : level === MAC_PRESSURE_CRITICAL ? "critical" : `level ${level}`;
|
|
21180
21296
|
const swapping = pagingBps === null ? "" : `, swapping out ${Math.round(pagingBps / 2 ** 20)} MB/s`;
|
|
21297
|
+
const full = compressor === null ? "" : `, compressor ${Math.round(compressor * 100)}% full`;
|
|
21298
|
+
const share = available / 100;
|
|
21299
|
+
const room = compressor === null ? 1 : 1 - compressor;
|
|
21181
21300
|
return {
|
|
21182
|
-
tight: level === MAC_PRESSURE_CRITICAL ||
|
|
21183
|
-
|
|
21301
|
+
tight: level === MAC_PRESSURE_CRITICAL || share < AVAILABLE_FLOOR || room < AVAILABLE_FLOOR || pagingBps !== null && pagingBps >= PAGING_FLOOR_BPS,
|
|
21302
|
+
hard: level === MAC_PRESSURE_CRITICAL || share <= AVAILABLE_FLOOR / 2 || room <= AVAILABLE_FLOOR / 2 || pagingBps !== null && pagingBps >= HARD_PAGING_BPS,
|
|
21303
|
+
why: `memory pressure ${pressure} with ${available}% available${swapping}${full}`
|
|
21184
21304
|
};
|
|
21185
21305
|
}
|
|
21186
21306
|
async function linux() {
|
|
21187
21307
|
let text;
|
|
21188
21308
|
try {
|
|
21189
21309
|
text = await Bun.file("/proc/meminfo").text();
|
|
21190
|
-
} catch {
|
|
21191
|
-
return
|
|
21310
|
+
} catch (e) {
|
|
21311
|
+
return { failed: `/proc/meminfo: ${e instanceof Error ? e.message : e}` };
|
|
21192
21312
|
}
|
|
21193
21313
|
const field = (name) => {
|
|
21194
21314
|
const m = text.match(new RegExp(`^${name}:\\s+(\\d+)`, "m"));
|
|
@@ -21197,9 +21317,13 @@ async function linux() {
|
|
|
21197
21317
|
const total = field("MemTotal");
|
|
21198
21318
|
const available = field("MemAvailable");
|
|
21199
21319
|
if (!total || available === null)
|
|
21200
|
-
return
|
|
21201
|
-
const
|
|
21202
|
-
return {
|
|
21320
|
+
return { failed: "/proc/meminfo names no MemTotal or MemAvailable" };
|
|
21321
|
+
const share = available / total;
|
|
21322
|
+
return {
|
|
21323
|
+
tight: share < AVAILABLE_FLOOR,
|
|
21324
|
+
hard: share <= AVAILABLE_FLOOR / 2,
|
|
21325
|
+
why: `${Math.round(share * 100)}% of memory available`
|
|
21326
|
+
};
|
|
21203
21327
|
}
|
|
21204
21328
|
async function sampleCosts(pgids) {
|
|
21205
21329
|
const costs = new Map;
|
|
@@ -22232,6 +22356,7 @@ function defaultAgent(wt, d, preview, onProcess) {
|
|
|
22232
22356
|
archived: { at: a.archivedAt, landed: a.landed, restorable: a.restorable }
|
|
22233
22357
|
}))
|
|
22234
22358
|
]),
|
|
22359
|
+
chatOf: (id) => d.chatFile?.(id),
|
|
22235
22360
|
onProcess,
|
|
22236
22361
|
spec,
|
|
22237
22362
|
connect: (app, spec, prepared) => spawnAcp(app, d.agents.launch(spec, prepared), wt.path, wt.id),
|
|
@@ -22608,6 +22733,9 @@ class RuntimeRegistry {
|
|
|
22608
22733
|
agentFor(id) {
|
|
22609
22734
|
return this.runtimes.get(id)?.agent;
|
|
22610
22735
|
}
|
|
22736
|
+
transcriptOf(id) {
|
|
22737
|
+
return (this.runtimes.get(id) ?? this.stopping.get(id))?.agent.transcript();
|
|
22738
|
+
}
|
|
22611
22739
|
ensureAgent(wt) {
|
|
22612
22740
|
const existing = this.runtimes.get(wt.id);
|
|
22613
22741
|
if (existing)
|
|
@@ -23573,13 +23701,14 @@ function lacksQuickModel(spec, state) {
|
|
|
23573
23701
|
const offered = state.cachedOptions(spec.id, "model");
|
|
23574
23702
|
return offered.length > 0 && !offered.some((m) => m.id === quick);
|
|
23575
23703
|
}
|
|
23704
|
+
var PLAN_READ_CHARS = 40000;
|
|
23576
23705
|
var PIECES_SYSTEM = "You read plans and reply with only the requested JSON.";
|
|
23577
23706
|
var piecesPrompt = (markdown) => [
|
|
23578
|
-
"Below is a plan. List the parts of
|
|
23579
|
-
`Reply with ONLY a JSON array of 0 to 6 objects, each {"title": a name for the part, ${NAME_SHAPE}, "section": the exact heading text of that part, "plan": the plan for that part's agent in markdown, from the plan's own words: what to build, why, the files, and what done looks like, 150 to 500 words, "where": "worktree" or "here", "after": optional, "this" when the plan says the part waits for this branch to land}.`,
|
|
23580
|
-
"Reply with an empty array when the plan is one piece of work with no part set apart.",
|
|
23707
|
+
"Below is a plan. List the parts of the work the plan itself means to be built apart from the rest: a step or section it marks as its own branch, a sibling worktree, a follow-up, or independent work, and work it puts off until after this one, to a later branch or a plan of its own, even where that is one sentence or one item of a list of what the plan leaves out. Something the plan only rules out, with no word that it is built later, is not a part. When you list any part, also list what the plan builds in this worktree, as one part.",
|
|
23708
|
+
`Reply with ONLY a JSON array of 0 to 6 objects, each {"title": a name for the part, ${NAME_SHAPE}, "section": the exact heading text of that part, or of the section that names it, "plan": the plan for that part's agent in markdown, from the plan's own words: what to build, why, the files, and what done looks like, 150 to 500 words, "where": "worktree" or "here", "after": optional, "this" when the plan says the part waits for this branch to land or comes after it}.`,
|
|
23709
|
+
"Reply with an empty array when the plan is one piece of work with no part set apart or put off.",
|
|
23581
23710
|
`Plan:
|
|
23582
|
-
${markdown.slice(0,
|
|
23711
|
+
${markdown.slice(0, PLAN_READ_CHARS)}`
|
|
23583
23712
|
].join(`
|
|
23584
23713
|
`);
|
|
23585
23714
|
function parsePieces(text) {
|
|
@@ -23839,6 +23968,7 @@ function sleepMsFrom(raw, inCloud) {
|
|
|
23839
23968
|
}
|
|
23840
23969
|
var DEFAULT_SLEEP_MS = sleepMsFrom(process.env.TOYON_PROC_SLEEP_MS, cloud.enabled);
|
|
23841
23970
|
var PRESSURE_EVERY_MS = 15000;
|
|
23971
|
+
var HARD_PICKS = 3;
|
|
23842
23972
|
var COSTS_EVERY_MS = 60000;
|
|
23843
23973
|
var STAGGER_MS = 3000;
|
|
23844
23974
|
|
|
@@ -23849,6 +23979,10 @@ class IdlePolicy {
|
|
|
23849
23979
|
queue = [];
|
|
23850
23980
|
draining = false;
|
|
23851
23981
|
drained = [];
|
|
23982
|
+
checking = false;
|
|
23983
|
+
freeing = null;
|
|
23984
|
+
eases = [];
|
|
23985
|
+
putDown = new Set;
|
|
23852
23986
|
sleepMs;
|
|
23853
23987
|
now;
|
|
23854
23988
|
constructor(d) {
|
|
@@ -23978,14 +24112,44 @@ class IdlePolicy {
|
|
|
23978
24112
|
return out;
|
|
23979
24113
|
}
|
|
23980
24114
|
async checkPressure() {
|
|
23981
|
-
|
|
23982
|
-
if (!signal?.tight)
|
|
23983
|
-
return;
|
|
23984
|
-
const pick = this.d.state.worktrees.filter((w) => this.candidate(w.id) && this.awakeProcs(w.id)).sort((a, b) => this.entry(a.id).activeAt - this.entry(b.id).activeAt)[0];
|
|
23985
|
-
if (!pick)
|
|
24115
|
+
if (this.checking)
|
|
23986
24116
|
return;
|
|
23987
|
-
|
|
23988
|
-
|
|
24117
|
+
this.checking = true;
|
|
24118
|
+
try {
|
|
24119
|
+
const signal = await (this.d.memory ?? memoryTight)();
|
|
24120
|
+
const picks = signal?.tight ? this.d.state.worktrees.filter((w) => this.candidate(w.id) && this.awakeProcs(w.id)).sort((a, b) => this.entry(a.id).activeAt - this.entry(b.id).activeAt).slice(0, signal.hard || this.eases.length > 0 ? HARD_PICKS : 1) : [];
|
|
24121
|
+
if (!signal?.tight)
|
|
24122
|
+
this.putDown.clear();
|
|
24123
|
+
if (signal && picks.some((p) => !this.putDown.has(p.id)))
|
|
24124
|
+
this.freeing = signal;
|
|
24125
|
+
else {
|
|
24126
|
+
this.freeing = null;
|
|
24127
|
+
this.letGo();
|
|
24128
|
+
}
|
|
24129
|
+
for (const pick of picks) {
|
|
24130
|
+
log.info("idle", `${signal?.why}; ${pick.id} is the least recently used`);
|
|
24131
|
+
this.putDown.add(pick.id);
|
|
24132
|
+
this.sleep(pick.id, "short of memory");
|
|
24133
|
+
}
|
|
24134
|
+
} catch (e) {
|
|
24135
|
+
this.freeing = null;
|
|
24136
|
+
this.letGo();
|
|
24137
|
+
throw e;
|
|
24138
|
+
} finally {
|
|
24139
|
+
this.checking = false;
|
|
24140
|
+
}
|
|
24141
|
+
}
|
|
24142
|
+
short() {
|
|
24143
|
+
return this.freeing?.why ?? null;
|
|
24144
|
+
}
|
|
24145
|
+
eased() {
|
|
24146
|
+
if (!this.freeing)
|
|
24147
|
+
return Promise.resolve();
|
|
24148
|
+
return new Promise((r) => this.eases.push(r));
|
|
24149
|
+
}
|
|
24150
|
+
letGo() {
|
|
24151
|
+
for (const go of this.eases.splice(0))
|
|
24152
|
+
go();
|
|
23989
24153
|
}
|
|
23990
24154
|
async sampleCosts() {
|
|
23991
24155
|
this.recordViewed();
|
|
@@ -27132,7 +27296,7 @@ function looksBinary(text) {
|
|
|
27132
27296
|
// ../daemon/src/files/service.ts
|
|
27133
27297
|
import { spawn as spawn5 } from "child_process";
|
|
27134
27298
|
import { lstat as lstat2, stat as stat3, unlink } from "fs/promises";
|
|
27135
|
-
import { join as join24 } from "path";
|
|
27299
|
+
import { dirname as dirname10, join as join24 } from "path";
|
|
27136
27300
|
|
|
27137
27301
|
// ../daemon/src/git/forge.ts
|
|
27138
27302
|
function githubRepoUrl(remoteUrl) {
|
|
@@ -28021,7 +28185,21 @@ class FileService {
|
|
|
28021
28185
|
child.on("error", () => {});
|
|
28022
28186
|
child.unref();
|
|
28023
28187
|
}
|
|
28188
|
+
async clip(worktreeId, path) {
|
|
28189
|
+
const target = resolveInside(this.require(worktreeId).path, path);
|
|
28190
|
+
if (!await stat3(target).catch(() => null))
|
|
28191
|
+
throw new UserError(`${path} is no longer there`);
|
|
28192
|
+
await this.clipPath(target);
|
|
28193
|
+
}
|
|
28194
|
+
async clipPath(target) {
|
|
28195
|
+
if (process.platform !== "darwin")
|
|
28196
|
+
throw new UserError("copying a file is only available on macOS");
|
|
28197
|
+
const r = await run("osascript", [...CLIP_FILE.flatMap((line) => ["-e", line]), target], dirname10(target));
|
|
28198
|
+
if (!r.ok)
|
|
28199
|
+
throw new UserError(`the file was not copied: ${r.err.trim() || `osascript exited ${r.exit}`}`);
|
|
28200
|
+
}
|
|
28024
28201
|
}
|
|
28202
|
+
var CLIP_FILE = ["on run argv", "set the clipboard to (POSIX file (item 1 of argv))", "end run"];
|
|
28025
28203
|
|
|
28026
28204
|
// ../daemon/src/files/open.ts
|
|
28027
28205
|
var PENDING_MS = 30000;
|
|
@@ -28659,7 +28837,7 @@ class AfterLand {
|
|
|
28659
28837
|
// ../daemon/src/repos/registry.ts
|
|
28660
28838
|
import { existsSync as existsSync28, mkdirSync as mkdirSync8, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "fs";
|
|
28661
28839
|
import { homedir as homedir10 } from "os";
|
|
28662
|
-
import { basename as basename9, dirname as
|
|
28840
|
+
import { basename as basename9, dirname as dirname11, join as join25 } from "path";
|
|
28663
28841
|
|
|
28664
28842
|
// ../daemon/src/git/base.ts
|
|
28665
28843
|
async function resolveBase(repoPath, defaultBranch, route) {
|
|
@@ -28930,7 +29108,7 @@ class RepoRegistry {
|
|
|
28930
29108
|
await excludeFromGit(repo.path, rel);
|
|
28931
29109
|
const file = join25(repo.path, rel);
|
|
28932
29110
|
try {
|
|
28933
|
-
mkdirSync8(
|
|
29111
|
+
mkdirSync8(dirname11(file), { recursive: true });
|
|
28934
29112
|
writeFileSync11(file, configText(repo.path, rel, config));
|
|
28935
29113
|
} catch (e) {
|
|
28936
29114
|
log.warn(repoId, `could not write ${rel}`, e);
|
|
@@ -30481,6 +30659,7 @@ async function withSlot(key, max, fn) {
|
|
|
30481
30659
|
|
|
30482
30660
|
// ../daemon/src/runs/service.ts
|
|
30483
30661
|
var DETACHED_POLL_MS = 5000;
|
|
30662
|
+
var SHORT_OF_MEMORY = "short of memory";
|
|
30484
30663
|
|
|
30485
30664
|
class RunService {
|
|
30486
30665
|
d;
|
|
@@ -30491,9 +30670,10 @@ class RunService {
|
|
|
30491
30670
|
}
|
|
30492
30671
|
begin(worktreeId, kind, opts) {
|
|
30493
30672
|
const key = keyOf2(worktreeId, kind);
|
|
30673
|
+
const memory = opts.memory ? this.d.memory : undefined;
|
|
30494
30674
|
const run = {
|
|
30495
30675
|
kind,
|
|
30496
|
-
status: opts.queued || opts.slot ? "queued" : "running",
|
|
30676
|
+
status: opts.queued || opts.slot || memory?.short() ? "queued" : "running",
|
|
30497
30677
|
since: Date.now(),
|
|
30498
30678
|
timeoutMs: opts.timeoutMs,
|
|
30499
30679
|
...opts.stage ? { stage: opts.stage } : {}
|
|
@@ -30510,6 +30690,30 @@ class RunService {
|
|
|
30510
30690
|
run.since = Date.now();
|
|
30511
30691
|
this.write(worktreeId, run);
|
|
30512
30692
|
};
|
|
30693
|
+
const stage = (text) => {
|
|
30694
|
+
if (!mine() || run.stage === text)
|
|
30695
|
+
return;
|
|
30696
|
+
if (text)
|
|
30697
|
+
run.stage = text;
|
|
30698
|
+
else
|
|
30699
|
+
delete run.stage;
|
|
30700
|
+
this.write(worktreeId, run);
|
|
30701
|
+
};
|
|
30702
|
+
const roomy = async () => {
|
|
30703
|
+
const why = memory?.short();
|
|
30704
|
+
if (!memory || !why)
|
|
30705
|
+
return mine();
|
|
30706
|
+
const was = run.stage;
|
|
30707
|
+
const since = Date.now();
|
|
30708
|
+
log.info(worktreeId, `${kind} waits for memory: ${why}`);
|
|
30709
|
+
stage(SHORT_OF_MEMORY);
|
|
30710
|
+
await memory.eased();
|
|
30711
|
+
if (!mine())
|
|
30712
|
+
return false;
|
|
30713
|
+
log.info(worktreeId, `${kind} starts after ${describeDuration(Date.now() - since)} waiting for memory`);
|
|
30714
|
+
stage(was);
|
|
30715
|
+
return true;
|
|
30716
|
+
};
|
|
30513
30717
|
let admitted = Promise.resolve(true);
|
|
30514
30718
|
if (opts.slot) {
|
|
30515
30719
|
let admit = () => {};
|
|
@@ -30519,8 +30723,8 @@ class RunService {
|
|
|
30519
30723
|
const held = new Promise((r) => {
|
|
30520
30724
|
live.release = r;
|
|
30521
30725
|
});
|
|
30522
|
-
fireAndForget(worktreeId, withSlot(opts.slot.key, opts.slot.max, () => {
|
|
30523
|
-
if (!mine()) {
|
|
30726
|
+
fireAndForget(worktreeId, withSlot(opts.slot.key, opts.slot.max, async () => {
|
|
30727
|
+
if (!mine() || !await roomy()) {
|
|
30524
30728
|
admit(false);
|
|
30525
30729
|
return;
|
|
30526
30730
|
}
|
|
@@ -30528,6 +30732,12 @@ class RunService {
|
|
|
30528
30732
|
admit(true);
|
|
30529
30733
|
return held;
|
|
30530
30734
|
}), `${kind} slot`);
|
|
30735
|
+
} else if (run.status === "queued" && !opts.queued) {
|
|
30736
|
+
admitted = roomy().then((ok) => {
|
|
30737
|
+
if (ok)
|
|
30738
|
+
start();
|
|
30739
|
+
return ok;
|
|
30740
|
+
});
|
|
30531
30741
|
}
|
|
30532
30742
|
return {
|
|
30533
30743
|
admitted,
|
|
@@ -30539,15 +30749,7 @@ class RunService {
|
|
|
30539
30749
|
run.pid = pgid;
|
|
30540
30750
|
this.write(worktreeId, run);
|
|
30541
30751
|
},
|
|
30542
|
-
stage
|
|
30543
|
-
if (!mine() || run.stage === text)
|
|
30544
|
-
return;
|
|
30545
|
-
if (text)
|
|
30546
|
-
run.stage = text;
|
|
30547
|
-
else
|
|
30548
|
-
delete run.stage;
|
|
30549
|
-
this.write(worktreeId, run);
|
|
30550
|
-
},
|
|
30752
|
+
stage,
|
|
30551
30753
|
finish: (exit) => {
|
|
30552
30754
|
if (!mine())
|
|
30553
30755
|
return;
|
|
@@ -31567,6 +31769,15 @@ function backfillOf(entries, since) {
|
|
|
31567
31769
|
}
|
|
31568
31770
|
var handlers = {
|
|
31569
31771
|
async subscribe(msg, ctx, s) {
|
|
31772
|
+
const going = s.worktrees.archivingTranscript(msg.worktreeId);
|
|
31773
|
+
if (going) {
|
|
31774
|
+
if (!ctx.subscribe(msg.worktreeId))
|
|
31775
|
+
return;
|
|
31776
|
+
ctx.reply({ t: "backfill", worktreeId: msg.worktreeId, ...backfillOf(going, msg.since), log: [] });
|
|
31777
|
+
ctx.reply({ t: "queue", worktreeId: msg.worktreeId, items: [] });
|
|
31778
|
+
ctx.reply({ t: "agent-commands", worktreeId: msg.worktreeId, commands: [] });
|
|
31779
|
+
return;
|
|
31780
|
+
}
|
|
31570
31781
|
const r = s.worktrees.readable(msg.worktreeId);
|
|
31571
31782
|
if (!r) {
|
|
31572
31783
|
const kept = s.worktrees.archivedTranscript(msg.worktreeId);
|
|
@@ -31837,7 +32048,7 @@ var handlers = {
|
|
|
31837
32048
|
},
|
|
31838
32049
|
async redraft(msg, _ctx, s) {
|
|
31839
32050
|
const r = s.worktrees.readable(msg.worktreeId);
|
|
31840
|
-
const events = r?.wt ? s.runtime.ensureAgent(r.wt).agent.transcript() : s.worktrees.archivedTranscript(msg.worktreeId) ?? [];
|
|
32051
|
+
const events = s.worktrees.archivingTranscript(msg.worktreeId) ?? (r?.wt ? s.runtime.ensureAgent(r.wt).agent.transcript() : s.worktrees.archivedTranscript(msg.worktreeId) ?? []);
|
|
31841
32052
|
const event = events.find((e) => e.seq === msg.seq)?.event;
|
|
31842
32053
|
if (event?.type !== "user-message")
|
|
31843
32054
|
throw new UserError("that message is no longer in this chat");
|
|
@@ -31862,6 +32073,12 @@ var handlers = {
|
|
|
31862
32073
|
async "reveal-path"(msg, _ctx, s) {
|
|
31863
32074
|
s.files.revealPath(await s.opens.where(msg.path));
|
|
31864
32075
|
},
|
|
32076
|
+
async "copy-file"(msg, _ctx, s) {
|
|
32077
|
+
await s.files.clip(msg.worktreeId, msg.path);
|
|
32078
|
+
},
|
|
32079
|
+
async "copy-file-path"(msg, _ctx, s) {
|
|
32080
|
+
await s.files.clipPath(await s.opens.where(msg.path));
|
|
32081
|
+
},
|
|
31865
32082
|
async "reveal-attachment"(msg, _ctx, s) {
|
|
31866
32083
|
const path = await s.attachments.locate(msg.worktreeId, msg.file, (file) => s.worktrees.archivedAttachment(msg.worktreeId, file));
|
|
31867
32084
|
if (!path)
|
|
@@ -33271,7 +33488,7 @@ function startServer(opts) {
|
|
|
33271
33488
|
// ../daemon/src/themes/store.ts
|
|
33272
33489
|
import { existsSync as existsSync32, readdirSync as readdirSync8, readFileSync as readFileSync18, statSync as statSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
33273
33490
|
import { homedir as homedir13 } from "os";
|
|
33274
|
-
import { basename as basename10, dirname as
|
|
33491
|
+
import { basename as basename10, dirname as dirname12, join as join29, resolve as resolve11 } from "path";
|
|
33275
33492
|
var home = homedir13();
|
|
33276
33493
|
var defaultExtensionDirs = [
|
|
33277
33494
|
join29(home, ".vscode", "extensions"),
|
|
@@ -33377,7 +33594,7 @@ function readVscodeTheme(file, depth = 0) {
|
|
|
33377
33594
|
throw new ThemeImportError(`${file}: not a JSON object`);
|
|
33378
33595
|
const t = json;
|
|
33379
33596
|
if (typeof t.include === "string" && depth < 5) {
|
|
33380
|
-
const parent = readVscodeTheme(resolve11(
|
|
33597
|
+
const parent = readVscodeTheme(resolve11(dirname12(file), t.include), depth + 1);
|
|
33381
33598
|
return {
|
|
33382
33599
|
...parent,
|
|
33383
33600
|
...t,
|
|
@@ -33928,10 +34145,10 @@ import { join as join30 } from "path";
|
|
|
33928
34145
|
|
|
33929
34146
|
// ../daemon/src/core/clone.ts
|
|
33930
34147
|
import { mkdir as mkdir4, rm as rm4 } from "fs/promises";
|
|
33931
|
-
import { dirname as
|
|
34148
|
+
import { dirname as dirname13 } from "path";
|
|
33932
34149
|
async function cloneTree(src, dst) {
|
|
33933
34150
|
const started = Date.now();
|
|
33934
|
-
await mkdir4(
|
|
34151
|
+
await mkdir4(dirname13(dst), { recursive: true });
|
|
33935
34152
|
const attempts = [
|
|
33936
34153
|
["clonefile", ["-Rc", src, dst]],
|
|
33937
34154
|
["reflink", ["-R", "--reflink=auto", src, dst]],
|
|
@@ -33939,7 +34156,7 @@ async function cloneTree(src, dst) {
|
|
|
33939
34156
|
];
|
|
33940
34157
|
let error = "";
|
|
33941
34158
|
for (const [how, args] of attempts) {
|
|
33942
|
-
const r = await run("cp", args,
|
|
34159
|
+
const r = await run("cp", args, dirname13(dst));
|
|
33943
34160
|
if (r.ok)
|
|
33944
34161
|
return { ok: true, how, ms: Date.now() - started };
|
|
33945
34162
|
error = r.err.split(`
|
|
@@ -34521,6 +34738,19 @@ function handedOrigin(origin, project) {
|
|
|
34521
34738
|
...project ? { project } : {}
|
|
34522
34739
|
};
|
|
34523
34740
|
}
|
|
34741
|
+
function pieceLanded(landed, repo, to, mark, why = {}) {
|
|
34742
|
+
return {
|
|
34743
|
+
type: "piece-landed",
|
|
34744
|
+
worktreeId: landed.id,
|
|
34745
|
+
repoName: repo.name,
|
|
34746
|
+
title: landed.title,
|
|
34747
|
+
...landed.pr?.url ? { url: landed.pr.url } : {},
|
|
34748
|
+
...mark?.subjects.length ? { subjects: mark.subjects } : {},
|
|
34749
|
+
...to.repoId === landed.repoId ? { same: true } : {},
|
|
34750
|
+
...why,
|
|
34751
|
+
ts: Date.now()
|
|
34752
|
+
};
|
|
34753
|
+
}
|
|
34524
34754
|
var PIECES_MAX = 6;
|
|
34525
34755
|
var PLAN_MAX = 20000;
|
|
34526
34756
|
var ATTACH_MAX = 10;
|
|
@@ -34606,6 +34836,7 @@ function parsePieces2(args) {
|
|
|
34606
34836
|
}
|
|
34607
34837
|
var HELD = "Handed to the person; the list shows beside your plan and they decide where each piece goes. Ask for plan approval now.";
|
|
34608
34838
|
var SHOWN = "Handed to the person as a card; they decide. Finish your turn.";
|
|
34839
|
+
var SHOWN_AWAY = "Handed to the person as a card; they decide, and none of it is yours unless Toyon says they kept it here. Do not wait for their answer: go on with the work you already have that does not depend on it, and finish your turn when that is done.";
|
|
34609
34840
|
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.";
|
|
34610
34841
|
function openPlanCard(entries) {
|
|
34611
34842
|
const open = new Map;
|
|
@@ -34739,6 +34970,14 @@ var KIND_OF = {
|
|
|
34739
34970
|
file: "file",
|
|
34740
34971
|
element: "pick"
|
|
34741
34972
|
};
|
|
34973
|
+
function wokeBy(landed, repo, to) {
|
|
34974
|
+
return {
|
|
34975
|
+
title: landed.title,
|
|
34976
|
+
branch: landed.branch,
|
|
34977
|
+
...landed.pr?.url ? { url: landed.pr.url } : {},
|
|
34978
|
+
...to.repoId === landed.repoId ? {} : { project: repo.name }
|
|
34979
|
+
};
|
|
34980
|
+
}
|
|
34742
34981
|
|
|
34743
34982
|
class HandoffService {
|
|
34744
34983
|
d;
|
|
@@ -34748,13 +34987,18 @@ class HandoffService {
|
|
|
34748
34987
|
d.hub.on("handoffDecided", (worktreeId, id, approved) => fireAndForget(worktreeId, approved ? this.act(worktreeId, id, undefined, true) : this.decline(worktreeId, id), "handoff"));
|
|
34749
34988
|
d.hub.on("planUnsplit", (worktreeId, markdown, planPath) => fireAndForget(worktreeId, this.read(worktreeId, markdown, planPath), "pieces read off the plan"));
|
|
34750
34989
|
d.hub.on("treeChanged", (worktreeId) => fireAndForget(worktreeId, this.stale(worktreeId), "pieces stale"));
|
|
34751
|
-
d.hub.on("landed", (worktreeId) => fireAndForget(worktreeId, this.landed(worktreeId), "pieces after landing"));
|
|
34990
|
+
d.hub.on("landed", (worktreeId, mark) => fireAndForget(worktreeId, this.landed(worktreeId, mark), "pieces after landing"));
|
|
34752
34991
|
d.hub.on("worktreesChanged", () => this.sweep());
|
|
34753
34992
|
this.sweep();
|
|
34754
34993
|
}
|
|
34755
34994
|
siblings(wt, repoId = wt.repoId) {
|
|
34756
34995
|
return this.d.state.worktrees.filter((w) => w.repoId === repoId && w.kind === "worktree" && w.id !== wt.id);
|
|
34757
34996
|
}
|
|
34997
|
+
takesSend(w) {
|
|
34998
|
+
if (!w.landed)
|
|
34999
|
+
return true;
|
|
35000
|
+
return (this.d.counts(w.id)?.dirty ?? 0) > 0 || isBusy(this.d.runtime.agentFor(w.id)?.status);
|
|
35001
|
+
}
|
|
34758
35002
|
otherProjects(wt) {
|
|
34759
35003
|
return this.d.state.repos.filter((r) => r.id !== wt.repoId);
|
|
34760
35004
|
}
|
|
@@ -34782,7 +35026,8 @@ class HandoffService {
|
|
|
34782
35026
|
}
|
|
34783
35027
|
tool(worktreeId) {
|
|
34784
35028
|
const wt = this.d.state.worktree(worktreeId);
|
|
34785
|
-
const
|
|
35029
|
+
const open = wt ? this.siblings(wt).filter((w) => this.takesSend(w)) : [];
|
|
35030
|
+
const names = open.map((w) => `${w.branch}, "${w.title}"`);
|
|
34786
35031
|
const projects = wt ? this.otherProjects(wt).map((r) => r.name) : [];
|
|
34787
35032
|
return handoffTool(names, projects);
|
|
34788
35033
|
}
|
|
@@ -34881,7 +35126,8 @@ class HandoffService {
|
|
|
34881
35126
|
const q = p.to?.trim() ?? "";
|
|
34882
35127
|
if (!q)
|
|
34883
35128
|
return refused(`Piece ${i + 1} ("${title}") is \`send\` and names no worktree in \`to\`.`);
|
|
34884
|
-
const
|
|
35129
|
+
const all = this.siblings(wt, piece.project?.id);
|
|
35130
|
+
const found = this.named(all.filter((w) => this.takesSend(w)), q, piece.project?.name, all.filter((w) => !this.takesSend(w)));
|
|
34885
35131
|
if ("refused" in found)
|
|
34886
35132
|
return refused(`Piece ${i + 1} ("${title}"): ${found.refused}`);
|
|
34887
35133
|
piece.to = found.worktree;
|
|
@@ -34924,7 +35170,8 @@ class HandoffService {
|
|
|
34924
35170
|
}
|
|
34925
35171
|
}
|
|
34926
35172
|
const shown = cut.length ? `${sentenceCase(cut.join("; "))}. ` : "";
|
|
34927
|
-
|
|
35173
|
+
const own = pieces.some((p) => p.fate === "here");
|
|
35174
|
+
return { text: shown + (held ? HELD : here ? SHOWN_HERE : own ? SHOWN : SHOWN_AWAY) };
|
|
34928
35175
|
}
|
|
34929
35176
|
offerHere(wt, pieces) {
|
|
34930
35177
|
if (pieces.length !== 1)
|
|
@@ -34956,12 +35203,20 @@ class HandoffService {
|
|
|
34956
35203
|
return found;
|
|
34957
35204
|
return { after: found.worktree };
|
|
34958
35205
|
}
|
|
34959
|
-
named(among, q, where = "this project") {
|
|
35206
|
+
named(among, q, where = "this project", done = []) {
|
|
34960
35207
|
const bare = q.startsWith("@") ? q.slice(1) : q;
|
|
34961
35208
|
const list = among.map((w) => `${w.branch}, "${w.title}"`).join("; ");
|
|
35209
|
+
const keys = [(w) => w.branch, (w) => w.title];
|
|
35210
|
+
const found = matchBy(among, bare, keys);
|
|
35211
|
+
const landed = found.length === 0 ? matchBy(done, bare, keys) : [];
|
|
35212
|
+
if (landed.length > 0) {
|
|
35213
|
+
const names = landed.map((w) => `"${w.title}" (${w.branch})`).join(", ");
|
|
35214
|
+
return {
|
|
35215
|
+
refused: `${names} landed and ${landed.length === 1 ? "holds" : "hold"} no new work, so no piece is sent there; a \`worktree\` piece starts a new one.`
|
|
35216
|
+
};
|
|
35217
|
+
}
|
|
34962
35218
|
if (among.length === 0)
|
|
34963
35219
|
return { refused: `no other worktree of ${where} is open.` };
|
|
34964
|
-
const found = matchBy(among, bare, [(w) => w.branch, (w) => w.title]);
|
|
34965
35220
|
if (found.length === 1) {
|
|
34966
35221
|
const w = found[0];
|
|
34967
35222
|
return { worktree: { id: w.id, branch: w.branch, title: w.title } };
|
|
@@ -35223,6 +35478,7 @@ ${p.section}`;
|
|
|
35223
35478
|
}
|
|
35224
35479
|
});
|
|
35225
35480
|
this.d.prompted(target.id);
|
|
35481
|
+
this.recordSender(target, wt.id);
|
|
35226
35482
|
became.set(piece.id, { fate, worktree: { id: target.id, branch: target.branch, title: target.title } });
|
|
35227
35483
|
return { pieceId: piece.id, fate, worktreeId: target.id, title: target.title, ...crossed };
|
|
35228
35484
|
}
|
|
@@ -35287,6 +35543,12 @@ ${p.section}`;
|
|
|
35287
35543
|
});
|
|
35288
35544
|
});
|
|
35289
35545
|
}
|
|
35546
|
+
recordSender(target, senderId) {
|
|
35547
|
+
if (target.senders?.includes(senderId))
|
|
35548
|
+
return;
|
|
35549
|
+
target.senders = [...target.senders ?? [], senderId];
|
|
35550
|
+
this.d.state.save();
|
|
35551
|
+
}
|
|
35290
35552
|
recordAwaits(originId, awaits) {
|
|
35291
35553
|
if (awaits.length === 0)
|
|
35292
35554
|
return;
|
|
@@ -35371,11 +35633,12 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
35371
35633
|
}
|
|
35372
35634
|
return out;
|
|
35373
35635
|
}
|
|
35374
|
-
async landed(landedId) {
|
|
35636
|
+
async landed(landedId, mark) {
|
|
35375
35637
|
const told = new Set;
|
|
35376
|
-
const origin = this.tellOrigin(landedId);
|
|
35638
|
+
const origin = this.tellOrigin(landedId, mark);
|
|
35377
35639
|
if (origin)
|
|
35378
35640
|
told.add(origin);
|
|
35641
|
+
const senders = this.tellSenders(landedId, told, mark);
|
|
35379
35642
|
const madeNow = new Set;
|
|
35380
35643
|
for (const origin of this.d.state.worktrees) {
|
|
35381
35644
|
const due = origin.queued?.filter((q) => !("piece" in q.after) && q.after.id === landedId) ?? [];
|
|
@@ -35414,17 +35677,40 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
35414
35677
|
}
|
|
35415
35678
|
}
|
|
35416
35679
|
}
|
|
35417
|
-
for (const id of this.tellWaiters(landedId, madeNow))
|
|
35680
|
+
for (const id of this.tellWaiters(landedId, madeNow, mark))
|
|
35418
35681
|
told.add(id);
|
|
35419
|
-
this.tellAwaiting(landedId, told);
|
|
35682
|
+
const awaited = this.tellAwaiting(landedId, told, mark);
|
|
35683
|
+
const woken = origin && !awaited.has(origin) && this.wakeHanded(origin, landedId) ? origin : null;
|
|
35684
|
+
for (const id of senders)
|
|
35685
|
+
if (id !== woken && !awaited.has(id))
|
|
35686
|
+
this.wakeHanded(id, landedId, true);
|
|
35687
|
+
}
|
|
35688
|
+
wakeHanded(toId, landedId, sent = false) {
|
|
35689
|
+
const to = this.d.state.worktree(toId);
|
|
35690
|
+
const landed = this.d.state.worktree(landedId);
|
|
35691
|
+
const repo = landed ? this.d.state.repo(landed.repoId) : undefined;
|
|
35692
|
+
if (to?.kind !== "worktree" || !landed || !repo)
|
|
35693
|
+
return false;
|
|
35694
|
+
if (to.awaits?.length || !this.takesSend(to))
|
|
35695
|
+
return false;
|
|
35696
|
+
if (!sent && (landed.lands?.length ?? 0) > 1)
|
|
35697
|
+
return false;
|
|
35698
|
+
this.wake(to, this.d.runtime.ensureAgent(to).agent, {
|
|
35699
|
+
...wokeBy(landed, repo, to),
|
|
35700
|
+
pieces: [],
|
|
35701
|
+
...sent ? { sent: true } : { handed: true }
|
|
35702
|
+
});
|
|
35703
|
+
return true;
|
|
35420
35704
|
}
|
|
35421
|
-
tellAwaiting(landedId, told) {
|
|
35705
|
+
tellAwaiting(landedId, told, mark) {
|
|
35422
35706
|
const landed = this.d.state.worktree(landedId);
|
|
35423
35707
|
const repo = landed ? this.d.state.repo(landed.repoId) : undefined;
|
|
35708
|
+
const awaited = new Set;
|
|
35424
35709
|
let changed = false;
|
|
35425
35710
|
for (const w of this.d.state.worktrees) {
|
|
35426
35711
|
if (!w.awaits?.some((a) => a.id === landedId))
|
|
35427
35712
|
continue;
|
|
35713
|
+
awaited.add(w.id);
|
|
35428
35714
|
const pieces = w.awaits.filter((a) => a.id === landedId).flatMap((a) => a.pieces ?? []);
|
|
35429
35715
|
w.awaits = w.awaits.filter((a) => a.id !== landedId);
|
|
35430
35716
|
if (w.awaits.length === 0)
|
|
@@ -35433,32 +35719,16 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
35433
35719
|
if (!landed || !repo || w.kind !== "worktree")
|
|
35434
35720
|
continue;
|
|
35435
35721
|
const agent = this.d.runtime.ensureAgent(w).agent;
|
|
35436
|
-
if (!told.has(w.id))
|
|
35437
|
-
agent.note({
|
|
35438
|
-
|
|
35439
|
-
|
|
35440
|
-
repoName: repo.name,
|
|
35441
|
-
title: landed.title,
|
|
35442
|
-
...landed.pr?.url ? { url: landed.pr.url } : {},
|
|
35443
|
-
...w.repoId === landed.repoId ? { same: true } : {},
|
|
35444
|
-
awaited: true,
|
|
35445
|
-
ts: Date.now()
|
|
35446
|
-
});
|
|
35447
|
-
}
|
|
35448
|
-
if (!w.landed) {
|
|
35449
|
-
this.wake(w, agent, {
|
|
35450
|
-
title: landed.title,
|
|
35451
|
-
branch: landed.branch,
|
|
35452
|
-
...landed.pr?.url ? { url: landed.pr.url } : {},
|
|
35453
|
-
...w.repoId === landed.repoId ? {} : { project: repo.name },
|
|
35454
|
-
pieces
|
|
35455
|
-
});
|
|
35456
|
-
}
|
|
35722
|
+
if (!told.has(w.id))
|
|
35723
|
+
agent.note(pieceLanded(landed, repo, w, mark, { awaited: true }));
|
|
35724
|
+
if (this.takesSend(w))
|
|
35725
|
+
this.wake(w, agent, { ...wokeBy(landed, repo, w), pieces });
|
|
35457
35726
|
}
|
|
35458
35727
|
if (changed) {
|
|
35459
35728
|
this.d.state.save();
|
|
35460
35729
|
this.d.hub.emit("worktreesChanged");
|
|
35461
35730
|
}
|
|
35731
|
+
return awaited;
|
|
35462
35732
|
}
|
|
35463
35733
|
wake(w, agent, woke) {
|
|
35464
35734
|
const pending = this.wakes.get(w.id);
|
|
@@ -35470,19 +35740,14 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
35470
35740
|
const landed = this.wakes.get(w.id) ?? [];
|
|
35471
35741
|
this.wakes.delete(w.id);
|
|
35472
35742
|
const live = this.d.state.worktree(w.id);
|
|
35473
|
-
if (live?.kind !== "worktree" || live
|
|
35743
|
+
if (live?.kind !== "worktree" || !this.takesSend(live))
|
|
35474
35744
|
return;
|
|
35475
35745
|
if (agent.transcript().slice(mark).some((e) => isMessage(e.event)))
|
|
35476
35746
|
return;
|
|
35477
35747
|
const titles = landed.map((l) => `"${l.title}"`).join(", ");
|
|
35478
35748
|
const pieces = new Set(landed.flatMap((l) => l.pieces));
|
|
35479
|
-
|
|
35480
|
-
|
|
35481
|
-
kind: "handoff",
|
|
35482
|
-
why: `${titles} landed, so the agent starts the ${pieces.size === 1 ? "piece" : "pieces"} that waited`
|
|
35483
|
-
},
|
|
35484
|
-
keepCards: true
|
|
35485
|
-
});
|
|
35749
|
+
const why = landed.every((l) => l.handed || l.sent) ? `${titles} landed, so the agent takes up what it handed off` : `${titles} landed, so the agent starts the ${pieces.size === 1 ? "piece" : "pieces"} that waited`;
|
|
35750
|
+
agent.send(pieceWakePrompt(landed), { asked: { kind: "handoff", why }, keepCards: true });
|
|
35486
35751
|
}), "piece woken");
|
|
35487
35752
|
}
|
|
35488
35753
|
repoint(origin, made, worktree) {
|
|
@@ -35525,7 +35790,7 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
35525
35790
|
this.dropDependents(live, q);
|
|
35526
35791
|
}
|
|
35527
35792
|
}
|
|
35528
|
-
tellOrigin(landedId) {
|
|
35793
|
+
tellOrigin(landedId, mark) {
|
|
35529
35794
|
const wt = this.d.state.worktree(landedId);
|
|
35530
35795
|
if (wt?.from?.kind !== "worktree")
|
|
35531
35796
|
return null;
|
|
@@ -35535,18 +35800,10 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
35535
35800
|
const repo = this.d.state.repo(wt.repoId);
|
|
35536
35801
|
if (!repo)
|
|
35537
35802
|
return null;
|
|
35538
|
-
this.d.runtime.ensureAgent(origin).agent.note(
|
|
35539
|
-
type: "piece-landed",
|
|
35540
|
-
worktreeId: wt.id,
|
|
35541
|
-
repoName: repo.name,
|
|
35542
|
-
title: wt.title,
|
|
35543
|
-
...wt.pr?.url ? { url: wt.pr.url } : {},
|
|
35544
|
-
...wt.repoId === origin.repoId ? { same: true } : {},
|
|
35545
|
-
ts: Date.now()
|
|
35546
|
-
});
|
|
35803
|
+
this.d.runtime.ensureAgent(origin).agent.note(pieceLanded(wt, repo, origin, mark));
|
|
35547
35804
|
return origin.id;
|
|
35548
35805
|
}
|
|
35549
|
-
tellWaiters(landedId, madeNow) {
|
|
35806
|
+
tellWaiters(landedId, madeNow, mark) {
|
|
35550
35807
|
const landed = this.d.state.worktree(landedId);
|
|
35551
35808
|
if (!landed)
|
|
35552
35809
|
return [];
|
|
@@ -35560,19 +35817,33 @@ ${handedPrompt(null, note, at, via)}`
|
|
|
35560
35817
|
if (w.from?.kind !== "worktree" || w.from.after?.id !== landedId)
|
|
35561
35818
|
continue;
|
|
35562
35819
|
told.push(w.id);
|
|
35563
|
-
this.d.runtime.ensureAgent(w).agent.note({
|
|
35564
|
-
type: "piece-landed",
|
|
35565
|
-
worktreeId: landed.id,
|
|
35566
|
-
repoName: repo.name,
|
|
35567
|
-
title: landed.title,
|
|
35568
|
-
...landed.pr?.url ? { url: landed.pr.url } : {},
|
|
35569
|
-
...w.repoId === landed.repoId ? { same: true } : {},
|
|
35570
|
-
again: true,
|
|
35571
|
-
ts: Date.now()
|
|
35572
|
-
});
|
|
35820
|
+
this.d.runtime.ensureAgent(w).agent.note(pieceLanded(landed, repo, w, mark, { again: true }));
|
|
35573
35821
|
}
|
|
35574
35822
|
return told;
|
|
35575
35823
|
}
|
|
35824
|
+
tellSenders(landedId, told, mark) {
|
|
35825
|
+
const wt = this.d.state.worktree(landedId);
|
|
35826
|
+
if (!wt?.senders)
|
|
35827
|
+
return [];
|
|
35828
|
+
const senders = wt.senders;
|
|
35829
|
+
delete wt.senders;
|
|
35830
|
+
this.d.state.save();
|
|
35831
|
+
const repo = this.d.state.repo(wt.repoId);
|
|
35832
|
+
if (!repo)
|
|
35833
|
+
return [];
|
|
35834
|
+
const open = [];
|
|
35835
|
+
for (const id of senders) {
|
|
35836
|
+
const sender = this.d.state.worktree(id);
|
|
35837
|
+
if (sender?.kind !== "worktree")
|
|
35838
|
+
continue;
|
|
35839
|
+
open.push(id);
|
|
35840
|
+
if (told.has(id) || sender.awaits?.some((a) => a.id === landedId))
|
|
35841
|
+
continue;
|
|
35842
|
+
told.add(id);
|
|
35843
|
+
this.d.runtime.ensureAgent(sender).agent.note(pieceLanded(wt, repo, sender, mark, { sent: true }));
|
|
35844
|
+
}
|
|
35845
|
+
return open;
|
|
35846
|
+
}
|
|
35576
35847
|
sweep() {
|
|
35577
35848
|
let changed = false;
|
|
35578
35849
|
for (const origin of this.d.state.worktrees) {
|
|
@@ -35859,7 +36130,8 @@ ${verdict.body}` : verdict.subject;
|
|
|
35859
36130
|
this.d.runs.drop(worktreeId, "check");
|
|
35860
36131
|
const run = this.d.runs.begin(worktreeId, "check", {
|
|
35861
36132
|
timeoutMs: ceiling,
|
|
35862
|
-
slot: { key: repo.path, max: CHECK_SLOTS }
|
|
36133
|
+
slot: { key: repo.path, max: CHECK_SLOTS },
|
|
36134
|
+
memory: true
|
|
35863
36135
|
});
|
|
35864
36136
|
if (!await run.admitted)
|
|
35865
36137
|
return;
|
|
@@ -35939,7 +36211,7 @@ ${verdict.body}` : verdict.subject;
|
|
|
35939
36211
|
this.judging.delete(worktreeId);
|
|
35940
36212
|
if (verdict)
|
|
35941
36213
|
this.recapTurn(worktreeId, verdict);
|
|
35942
|
-
log.info(worktreeId, `landing: check ${check}${landing.why ? ", doubted" : ""}${opts.ask ? "" : ", check only"}, ${Date.now() - started}ms${waited > 100 ? ` (waited ${waited}ms
|
|
36214
|
+
log.info(worktreeId, `landing: check ${check}${landing.why ? ", doubted" : ""}${opts.ask ? "" : ", check only"}, ${Date.now() - started}ms${waited > 100 ? ` (waited ${waited}ms to start)` : ""}`);
|
|
35943
36215
|
this.d.worktrees.setLanding(worktreeId, landing);
|
|
35944
36216
|
if (command && check === "fail" && ran && typeof ran.exit === "number" && !opts.quiet)
|
|
35945
36217
|
this.d.hub.emit("checkFailed", worktreeId, { toolId: ran.toolId, command, text: ran.text });
|
|
@@ -36145,7 +36417,7 @@ function clip2(text) {
|
|
|
36145
36417
|
|
|
36146
36418
|
// ../daemon/src/worktrees/outside.ts
|
|
36147
36419
|
import { stat as stat6 } from "fs/promises";
|
|
36148
|
-
import { basename as basename12, dirname as
|
|
36420
|
+
import { basename as basename12, dirname as dirname14, resolve as resolve12 } from "path";
|
|
36149
36421
|
|
|
36150
36422
|
// ../daemon/src/worktrees/discover.ts
|
|
36151
36423
|
import { createHash as createHash6 } from "crypto";
|
|
@@ -36318,7 +36590,7 @@ class OutsideSend {
|
|
|
36318
36590
|
const gitDir = common.ok && common.out ? canonical(common.out) : null;
|
|
36319
36591
|
if (!gitDir || basename12(gitDir) !== ".git")
|
|
36320
36592
|
throw new UserError(`${raw} keeps its repository apart from its checkout`);
|
|
36321
|
-
return { path, top, mainPath:
|
|
36593
|
+
return { path, top, mainPath: dirname14(gitDir) };
|
|
36322
36594
|
}
|
|
36323
36595
|
async start(repoId, text, context, carry) {
|
|
36324
36596
|
const repo = this.d.state.requireRepo(repoId);
|
|
@@ -36843,7 +37115,7 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
|
|
|
36843
37115
|
// ../daemon/src/worktrees/service.ts
|
|
36844
37116
|
import { cpSync, existsSync as existsSync38, readFileSync as readFileSync22, rmSync as rmSync9 } from "fs";
|
|
36845
37117
|
import { rm as rm6 } from "fs/promises";
|
|
36846
|
-
import { dirname as
|
|
37118
|
+
import { dirname as dirname16, join as join36 } from "path";
|
|
36847
37119
|
|
|
36848
37120
|
// ../daemon/src/git/archive.ts
|
|
36849
37121
|
import { rmSync as rmSync6 } from "fs";
|
|
@@ -37091,7 +37363,7 @@ async function dropTurnRefs(repoPath, worktreeId) {
|
|
|
37091
37363
|
|
|
37092
37364
|
// ../daemon/src/worktrees/archive.ts
|
|
37093
37365
|
import { existsSync as existsSync35, mkdirSync as mkdirSync9, readdirSync as readdirSync9, readFileSync as readFileSync21, renameSync as renameSync7, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
37094
|
-
import { basename as basename13, dirname as
|
|
37366
|
+
import { basename as basename13, dirname as dirname15, join as join33 } from "path";
|
|
37095
37367
|
class WorktreeArchive {
|
|
37096
37368
|
dir;
|
|
37097
37369
|
records = new Map;
|
|
@@ -37235,7 +37507,7 @@ function firstPrompt(transcript) {
|
|
|
37235
37507
|
function move(from, to) {
|
|
37236
37508
|
if (!existsSync35(from))
|
|
37237
37509
|
return;
|
|
37238
|
-
mkdirSync9(
|
|
37510
|
+
mkdirSync9(dirname15(to), { recursive: true });
|
|
37239
37511
|
rmSync8(to, { recursive: true, force: true });
|
|
37240
37512
|
renameSync7(from, to);
|
|
37241
37513
|
}
|
|
@@ -37886,9 +38158,11 @@ class Ship {
|
|
|
37886
38158
|
if (f)
|
|
37887
38159
|
this.setLanded(wt, landedNow(wt, f));
|
|
37888
38160
|
}
|
|
37889
|
-
async settleLanded(wt) {
|
|
38161
|
+
async settleLanded(wt, noted) {
|
|
37890
38162
|
const repo = this.d.state.repo(wt.repoId);
|
|
37891
38163
|
const f = wt.kind === "main" || !repo ? null : await this.landedFacts(wt);
|
|
38164
|
+
if (noted)
|
|
38165
|
+
wt.lands = [...wt.lands ?? [], noted];
|
|
37892
38166
|
delete wt.landing;
|
|
37893
38167
|
let held;
|
|
37894
38168
|
if (f && repo) {
|
|
@@ -37987,7 +38261,11 @@ ${wt.landing.body}` : s;
|
|
|
37987
38261
|
const where = cwd === wt.path ? "git" : `git -C ${cwd}`;
|
|
37988
38262
|
const commit = args[0] === "commit";
|
|
37989
38263
|
const ceiling = commit ? timeoutFor(repo.config, "commit") : STEP_TIMEOUT_MS;
|
|
37990
|
-
const handle = commit ? this.d.runs.begin(wt.id, "commit", { timeoutMs: ceiling }) : null;
|
|
38264
|
+
const handle = commit ? this.d.runs.begin(wt.id, "commit", { timeoutMs: ceiling, memory: true }) : null;
|
|
38265
|
+
if (handle && !await handle.admitted) {
|
|
38266
|
+
const err = "a newer commit took its place";
|
|
38267
|
+
return { ok: false, out: "", err, exit: err, text: "" };
|
|
38268
|
+
}
|
|
37991
38269
|
const run = (onText, signal) => stepRun(cwd, args, onText, signal, {
|
|
37992
38270
|
timeoutMs: ceiling,
|
|
37993
38271
|
...handle ? { onSpawn: handle.spawned } : {},
|
|
@@ -38087,7 +38365,7 @@ ${wt.landing.body}` : s;
|
|
|
38087
38365
|
ts: Date.now()
|
|
38088
38366
|
});
|
|
38089
38367
|
}
|
|
38090
|
-
this.d.hub.emit("landed", wt.id);
|
|
38368
|
+
this.d.hub.emit("landed", wt.id, o.mark);
|
|
38091
38369
|
}
|
|
38092
38370
|
async followBranch(wt, current) {
|
|
38093
38371
|
if (wt.kind === "main")
|
|
@@ -38322,13 +38600,13 @@ ${wt.landing.body}` : s;
|
|
|
38322
38600
|
return { result: { ok: true, message: `${did}; ${here}` }, landed: true, ...settled };
|
|
38323
38601
|
}
|
|
38324
38602
|
async settle(wt, repo, mark, opts = {}) {
|
|
38325
|
-
const noted = await this.
|
|
38603
|
+
const noted = await this.landMark(repo, wt, mark, opts);
|
|
38326
38604
|
if (!opts.stay)
|
|
38327
38605
|
await this.restartFromMain(wt, baseOf(repo));
|
|
38328
38606
|
this.d.tree.invalidateAll();
|
|
38329
|
-
return { mark: noted, held: await this.settleLanded(wt) };
|
|
38607
|
+
return { mark: noted, held: await this.settleLanded(wt, noted) };
|
|
38330
38608
|
}
|
|
38331
|
-
async
|
|
38609
|
+
async landMark(repo, wt, mark, opts = {}) {
|
|
38332
38610
|
if (!mark)
|
|
38333
38611
|
return;
|
|
38334
38612
|
const n = wt.lands?.length ?? 0;
|
|
@@ -38337,15 +38615,12 @@ ${wt.landing.body}` : s;
|
|
|
38337
38615
|
log.warn(wt.id, `could not keep the commits it landed: ${pinned.err}`);
|
|
38338
38616
|
return;
|
|
38339
38617
|
}
|
|
38340
|
-
|
|
38618
|
+
return {
|
|
38341
38619
|
...mark,
|
|
38342
38620
|
at: Date.now(),
|
|
38343
38621
|
...opts.pr ? { pr: opts.pr.number } : {},
|
|
38344
38622
|
subjects: await this.landedWhat(repo, wt, mark, opts)
|
|
38345
38623
|
};
|
|
38346
|
-
wt.lands = [...wt.lands ?? [], noted];
|
|
38347
|
-
this.d.state.save();
|
|
38348
|
-
return noted;
|
|
38349
38624
|
}
|
|
38350
38625
|
async landedWhat(repo, wt, mark, opts) {
|
|
38351
38626
|
if (opts.pr?.title)
|
|
@@ -40269,6 +40544,11 @@ class WorktreeService {
|
|
|
40269
40544
|
archivingNow(worktreeId) {
|
|
40270
40545
|
return this.archiving.get(worktreeId);
|
|
40271
40546
|
}
|
|
40547
|
+
archivingTranscript(worktreeId) {
|
|
40548
|
+
if (!this.archiving.has(worktreeId))
|
|
40549
|
+
return null;
|
|
40550
|
+
return this.d.runtime.transcriptOf(worktreeId) ?? new Transcript(this.chatFiles(worktreeId).transcript, worktreeId).entries;
|
|
40551
|
+
}
|
|
40272
40552
|
async discardWorktree(worktreeId) {
|
|
40273
40553
|
const wt = this.d.state.worktree(worktreeId);
|
|
40274
40554
|
if (!wt || !(canArchive(wt) || wt.kind === "spare"))
|
|
@@ -40305,6 +40585,8 @@ class WorktreeService {
|
|
|
40305
40585
|
rmSync9(this.plansScratch(wt.id), { recursive: true, force: true });
|
|
40306
40586
|
throw e;
|
|
40307
40587
|
}
|
|
40588
|
+
if (this.d.watches)
|
|
40589
|
+
fireAndForget(worktreeId, this.d.watches.drop(wt.path), "watchman watch drop");
|
|
40308
40590
|
if (hasOwnBranch(wt)) {
|
|
40309
40591
|
const r = await git(repo.path, "branch", "-D", wt.branch);
|
|
40310
40592
|
if (!r.ok)
|
|
@@ -40386,7 +40668,7 @@ class WorktreeService {
|
|
|
40386
40668
|
throw new Error(`git worktree remove --force ${wt.path} failed (${r.exit}): ${r.err}`);
|
|
40387
40669
|
if (await deleteTree(wt.path))
|
|
40388
40670
|
return;
|
|
40389
|
-
await run("chmod", ["-R", "u+w", wt.path],
|
|
40671
|
+
await run("chmod", ["-R", "u+w", wt.path], dirname16(wt.path));
|
|
40390
40672
|
if (!await deleteTree(wt.path))
|
|
40391
40673
|
log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
|
|
40392
40674
|
}
|
|
@@ -40574,6 +40856,9 @@ class WorktreeService {
|
|
|
40574
40856
|
return this.d.state.repo(rec.worktree.repoId) ?? this.d.state.repos.find((r) => r.path === rec.repoPath);
|
|
40575
40857
|
}
|
|
40576
40858
|
async restore(archiveId, createdBy, message) {
|
|
40859
|
+
const going = this.archivingNow(archiveId);
|
|
40860
|
+
if (going)
|
|
40861
|
+
await going.catch(() => {});
|
|
40577
40862
|
const running = this.restoring.get(archiveId);
|
|
40578
40863
|
const live = running ? await running : this.d.state.worktree(archiveId);
|
|
40579
40864
|
if (live) {
|
|
@@ -40603,7 +40888,7 @@ class WorktreeService {
|
|
|
40603
40888
|
throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
|
|
40604
40889
|
if (rec.auto)
|
|
40605
40890
|
log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
|
|
40606
|
-
const path = existsSync38(old.path) ? join36(
|
|
40891
|
+
const path = existsSync38(old.path) ? join36(dirname16(old.path), freeSlot(dirname16(old.path)).dir) : old.path;
|
|
40607
40892
|
const wt = {
|
|
40608
40893
|
...old,
|
|
40609
40894
|
repoId: repo.id,
|
|
@@ -41184,9 +41469,13 @@ class WorktreeService {
|
|
|
41184
41469
|
const path = transcriptPathFor(this.d.paths.transcriptsDir, worktreeId);
|
|
41185
41470
|
return existsSync38(path) ? path : undefined;
|
|
41186
41471
|
}
|
|
41187
|
-
|
|
41472
|
+
chatFile(id) {
|
|
41188
41473
|
const path = this.d.state.worktree(id) ? this.writtenTranscript(id) : this.archive.chatFiles(id)?.transcript;
|
|
41189
|
-
|
|
41474
|
+
return path && existsSync38(path) ? path : undefined;
|
|
41475
|
+
}
|
|
41476
|
+
transcriptPath(id) {
|
|
41477
|
+
const path = this.chatFile(id);
|
|
41478
|
+
if (!path)
|
|
41190
41479
|
throw new UserError("nothing has been written to this chat yet");
|
|
41191
41480
|
return path;
|
|
41192
41481
|
}
|
|
@@ -41330,6 +41619,100 @@ class WorktreeService {
|
|
|
41330
41619
|
}
|
|
41331
41620
|
}
|
|
41332
41621
|
|
|
41622
|
+
// ../daemon/src/worktrees/stop.ts
|
|
41623
|
+
var REASON_MAX = 300;
|
|
41624
|
+
var SHAPE = "stop takes { worktree: string, reason: string }.";
|
|
41625
|
+
var refused4 = (text) => ({ text, isError: true });
|
|
41626
|
+
|
|
41627
|
+
class StopTool {
|
|
41628
|
+
d;
|
|
41629
|
+
constructor(d) {
|
|
41630
|
+
this.d = d;
|
|
41631
|
+
}
|
|
41632
|
+
tool(worktreeId) {
|
|
41633
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
41634
|
+
const made = wt ? this.made(wt) : [];
|
|
41635
|
+
const running = made.filter((w) => this.running(w));
|
|
41636
|
+
const now = made.length === 0 ? "Right now no open worktree was made from a piece of this chat's work." : running.length === 0 ? "Right now none of the worktrees made from this chat's pieces is running a turn." : `Right now you can stop: ${running.map((w) => this.named(wt, w)).join("; ")}.`;
|
|
41637
|
+
return {
|
|
41638
|
+
name: STOP_TOOL,
|
|
41639
|
+
description: `Stop the turn a worktree made from a piece you handed off is running, when its work should not go on: the person said to drop or pause it, or what it builds on changed. It is the stop the person presses and nothing more: the worktree, its files and its chat stay, that chat says this one stopped it and why, and the person can send it on from there. It does not archive or remove the worktree, which is the person's to do in Toyon, and it reaches only worktrees made from this chat's pieces, never one a piece was sent into. ${now}`,
|
|
41640
|
+
inputSchema: {
|
|
41641
|
+
type: "object",
|
|
41642
|
+
properties: {
|
|
41643
|
+
worktree: { type: "string", description: "The worktree to stop, by its branch or its title." },
|
|
41644
|
+
reason: {
|
|
41645
|
+
type: "string",
|
|
41646
|
+
description: `Why, in a sentence of at most ${REASON_MAX} characters: the person reads it on that chat's stopped row.`
|
|
41647
|
+
}
|
|
41648
|
+
},
|
|
41649
|
+
required: ["worktree", "reason"],
|
|
41650
|
+
additionalProperties: false
|
|
41651
|
+
}
|
|
41652
|
+
};
|
|
41653
|
+
}
|
|
41654
|
+
call(worktreeId, args) {
|
|
41655
|
+
const a = args && typeof args === "object" && !Array.isArray(args) ? args : null;
|
|
41656
|
+
if (!a || typeof a.worktree !== "string" || typeof a.reason !== "string")
|
|
41657
|
+
return refused4(SHAPE);
|
|
41658
|
+
const wt = this.d.state.worktree(worktreeId);
|
|
41659
|
+
if (!wt)
|
|
41660
|
+
return refused4("unknown worktree");
|
|
41661
|
+
const reason = a.reason.trim();
|
|
41662
|
+
if (!reason)
|
|
41663
|
+
return refused4("Say why in `reason`: the person reads it on that chat's stopped row.");
|
|
41664
|
+
if (reason.length > REASON_MAX) {
|
|
41665
|
+
return refused4(`The reason is over ${REASON_MAX} characters; say it in a sentence.`);
|
|
41666
|
+
}
|
|
41667
|
+
const q = a.worktree.trim();
|
|
41668
|
+
const bare = q.startsWith("@") ? q.slice(1) : q;
|
|
41669
|
+
if (!bare)
|
|
41670
|
+
return refused4("Name the worktree to stop, by its branch or its title.");
|
|
41671
|
+
const made = this.made(wt);
|
|
41672
|
+
const keys = [(w) => w.branch, (w) => w.title];
|
|
41673
|
+
const found = matchBy(made, bare, keys);
|
|
41674
|
+
if (found.length > 1) {
|
|
41675
|
+
return refused4(`"${q}" names more than one worktree: ${found.map((w) => w.branch).join(", ")}. Say which by its branch.`);
|
|
41676
|
+
}
|
|
41677
|
+
const target = found[0];
|
|
41678
|
+
if (!target) {
|
|
41679
|
+
if (bare === "this" || matchBy([wt], bare, keys).length > 0) {
|
|
41680
|
+
return refused4("That is this worktree; end your own turn by finishing it.");
|
|
41681
|
+
}
|
|
41682
|
+
const other = matchBy(this.d.state.worktrees.filter((w) => w.id !== wt.id && w.kind === "worktree" && !made.includes(w)), bare, keys)[0];
|
|
41683
|
+
if (other) {
|
|
41684
|
+
return refused4(`"${other.title}" (${other.branch}) was not made from a piece of this chat's work, so it is the person's to stop. Tell them, and name it.`);
|
|
41685
|
+
}
|
|
41686
|
+
return refused4(made.length === 0 ? "No open worktree was made from a piece of this chat's work, so there is nothing here to stop." : `No worktree made from this chat's pieces is called "${q}". Those open: ${made.map((w) => this.named(wt, w)).join("; ")}.`);
|
|
41687
|
+
}
|
|
41688
|
+
const name = `"${target.title}" (${target.branch})`;
|
|
41689
|
+
const agent = this.d.agentFor(target.id);
|
|
41690
|
+
if (!agent || !this.running(target))
|
|
41691
|
+
return refused4(`${name} is not running a turn; there is nothing to stop.`);
|
|
41692
|
+
if (agent.stopping)
|
|
41693
|
+
return refused4(`${name} is stopping already.`);
|
|
41694
|
+
const waiting = agent.queueLength;
|
|
41695
|
+
const project = target.repoId === wt.repoId ? undefined : this.d.state.repo(wt.repoId)?.name;
|
|
41696
|
+
if (!agent.stop({ origin: handedOrigin(wt, project), reason })) {
|
|
41697
|
+
return refused4(`${name} is not running a turn; there is nothing to stop.`);
|
|
41698
|
+
}
|
|
41699
|
+
const next = waiting > 0 ? ` ${waiting === 1 ? "A message" : `${waiting} messages`} already waiting in that chat ${waiting === 1 ? "goes" : "go"} next, as after any stop.` : "";
|
|
41700
|
+
return {
|
|
41701
|
+
text: `${name} is stopping its turn. Its chat says this one stopped it and why; its worktree and files stay as they are, and the person can send it on or archive it in Toyon.${next}`
|
|
41702
|
+
};
|
|
41703
|
+
}
|
|
41704
|
+
made(wt) {
|
|
41705
|
+
return this.d.state.worktrees.filter((w) => w.kind === "worktree" && w.id !== wt.id && w.from?.kind === "worktree" && w.from.origin.id === wt.id);
|
|
41706
|
+
}
|
|
41707
|
+
running(w) {
|
|
41708
|
+
return isBusy(this.d.agentFor(w.id)?.status);
|
|
41709
|
+
}
|
|
41710
|
+
named(wt, w) {
|
|
41711
|
+
const repo = wt && w.repoId !== wt.repoId ? this.d.state.repo(w.repoId) : undefined;
|
|
41712
|
+
return `${w.branch}, "${w.title}"${repo ? ` in ${repo.name}` : ""}`;
|
|
41713
|
+
}
|
|
41714
|
+
}
|
|
41715
|
+
|
|
41333
41716
|
// ../daemon/src/worktrees/sweep.ts
|
|
41334
41717
|
var ARCHIVE_AFTER_MS = 2 * 60 * 60000;
|
|
41335
41718
|
var SWEEP_EVERY_MS = 10 * 60000;
|
|
@@ -41415,11 +41798,65 @@ class ArchiveSweep {
|
|
|
41415
41798
|
}
|
|
41416
41799
|
}
|
|
41417
41800
|
|
|
41801
|
+
// ../daemon/src/worktrees/watchman.ts
|
|
41802
|
+
import { existsSync as existsSync39 } from "fs";
|
|
41803
|
+
var NO_SERVER = ["--no-spawn", "--no-local"];
|
|
41804
|
+
var TIMEOUT_MS2 = 5000;
|
|
41805
|
+
|
|
41806
|
+
class WatchmanWatches {
|
|
41807
|
+
d;
|
|
41808
|
+
bin;
|
|
41809
|
+
constructor(d) {
|
|
41810
|
+
this.d = d;
|
|
41811
|
+
}
|
|
41812
|
+
async drop(path) {
|
|
41813
|
+
if (existsSync39(path))
|
|
41814
|
+
return;
|
|
41815
|
+
const gone = canonical(path);
|
|
41816
|
+
if (!within2(gone, canonical(this.d.root)))
|
|
41817
|
+
return;
|
|
41818
|
+
await this.del((root) => within2(root, gone));
|
|
41819
|
+
}
|
|
41820
|
+
async sweep() {
|
|
41821
|
+
await this.del((root) => !existsSync39(root));
|
|
41822
|
+
}
|
|
41823
|
+
async del(dead) {
|
|
41824
|
+
const home = canonical(this.d.root);
|
|
41825
|
+
let dropped = 0;
|
|
41826
|
+
for (const root of await this.roots()) {
|
|
41827
|
+
if (root === home || !within2(root, home) || !dead(root))
|
|
41828
|
+
continue;
|
|
41829
|
+
if ((await this.ask("watch-del", root))?.ok)
|
|
41830
|
+
dropped++;
|
|
41831
|
+
}
|
|
41832
|
+
if (dropped > 0)
|
|
41833
|
+
log.info("watchman", `dropped ${dropped} ${dropped === 1 ? "watch" : "watches"} whose worktree is gone`);
|
|
41834
|
+
}
|
|
41835
|
+
async roots() {
|
|
41836
|
+
const r = await this.ask("watch-list");
|
|
41837
|
+
if (!r?.ok)
|
|
41838
|
+
return [];
|
|
41839
|
+
try {
|
|
41840
|
+
const roots = JSON.parse(r.out).roots;
|
|
41841
|
+
return Array.isArray(roots) ? roots.filter((p) => typeof p === "string") : [];
|
|
41842
|
+
} catch {
|
|
41843
|
+
return [];
|
|
41844
|
+
}
|
|
41845
|
+
}
|
|
41846
|
+
async ask(...command) {
|
|
41847
|
+
if (this.bin === undefined)
|
|
41848
|
+
this.bin = (this.d.which ?? Bun.which)("watchman");
|
|
41849
|
+
if (!this.bin)
|
|
41850
|
+
return null;
|
|
41851
|
+
return (this.d.run ?? run)(this.bin, [...NO_SERVER, ...command], this.d.root, undefined, { timeoutMs: TIMEOUT_MS2 });
|
|
41852
|
+
}
|
|
41853
|
+
}
|
|
41854
|
+
|
|
41418
41855
|
// ../daemon/src/index.ts
|
|
41419
41856
|
process.on("unhandledRejection", (e) => log.error("daemon", "unhandled rejection", e));
|
|
41420
41857
|
process.on("uncaughtException", (e) => log.error("daemon", "uncaught exception", e));
|
|
41421
41858
|
log.info("daemon", `toyon ${package_default.version} on bun ${Bun.version}, pid ${process.pid}, ${process.argv[1]}`);
|
|
41422
|
-
var here =
|
|
41859
|
+
var here = dirname17(fileURLToPath(import.meta.url));
|
|
41423
41860
|
var {
|
|
41424
41861
|
shellDist: SHELL_DIST,
|
|
41425
41862
|
bridgeJs: BRIDGE_JS,
|
|
@@ -41513,6 +41950,7 @@ var runtime = new RuntimeRegistry({
|
|
|
41513
41950
|
pageErrors: (id) => pageErrors.ambient(id),
|
|
41514
41951
|
turnStarting: (id) => worktrees.turnStarted(id),
|
|
41515
41952
|
archived: (repoId) => worktrees.archived(repoId),
|
|
41953
|
+
chatFile: (id) => worktrees.chatFile(id),
|
|
41516
41954
|
standing: (id) => worktrees.standing(id),
|
|
41517
41955
|
tier: (id) => worktrees.tierStanding(id),
|
|
41518
41956
|
remote: () => setting.get(),
|
|
@@ -41528,9 +41966,14 @@ var drafts = new DraftStore({
|
|
|
41528
41966
|
hub,
|
|
41529
41967
|
uploads
|
|
41530
41968
|
});
|
|
41531
|
-
var runs = new RunService({
|
|
41969
|
+
var runs = new RunService({
|
|
41970
|
+
state,
|
|
41971
|
+
hub,
|
|
41972
|
+
memory: { short: () => idle.short(), eased: () => idle.eased() }
|
|
41973
|
+
});
|
|
41532
41974
|
var exec = new ExecService({ state, runtime, shipping: (id) => worktrees.shippingOf(id) });
|
|
41533
41975
|
var cache = new ArtifactCache({ paths, state, hub });
|
|
41976
|
+
var watches = new WatchmanWatches({ root: paths.worktreesDir });
|
|
41534
41977
|
var fix = new FixService({ state, hub, runtime });
|
|
41535
41978
|
var interest = new Interest;
|
|
41536
41979
|
var worktrees = new WorktreeService({
|
|
@@ -41547,7 +41990,8 @@ var worktrees = new WorktreeService({
|
|
|
41547
41990
|
pageErrors: (id) => pageErrors.ambient(id),
|
|
41548
41991
|
interested: (id) => interest.isSubscribed(id),
|
|
41549
41992
|
shown: (id) => idle.isShown(id),
|
|
41550
|
-
message: (wt) => landing.message(wt.id)
|
|
41993
|
+
message: (wt) => landing.message(wt.id),
|
|
41994
|
+
watches
|
|
41551
41995
|
});
|
|
41552
41996
|
interest.onFirst((id) => worktrees.tree.watch(id));
|
|
41553
41997
|
interest.onLast((id) => worktrees.tree.unwatch(id));
|
|
@@ -41571,6 +42015,7 @@ var handoff2 = new HandoffService({
|
|
|
41571
42015
|
tools.add(handoff2);
|
|
41572
42016
|
tools.add(new WorktreeLookup({ state, transcriptsDir: paths.transcriptsDir, archived: (repoId) => worktrees.archived(repoId) }));
|
|
41573
42017
|
tools.add(new OwnTool({ state, own: (id, names) => worktrees.ownProcs(id, names, { by: "agent" }) }));
|
|
42018
|
+
tools.add(new StopTool({ state, agentFor: (id) => runtime.agentFor(id) }));
|
|
41574
42019
|
var turns = new TurnService({
|
|
41575
42020
|
state,
|
|
41576
42021
|
hub,
|
|
@@ -41829,6 +42274,7 @@ idle.boot();
|
|
|
41829
42274
|
runs.boot();
|
|
41830
42275
|
landing.boot();
|
|
41831
42276
|
fireAndForget("cache", cache.sweep(), "cache sweep");
|
|
42277
|
+
fireAndForget("watchman", watches.sweep(), "watchman sweep");
|
|
41832
42278
|
fireAndForget("agents", agents.installMissing().then(() => readiness2.checkMissing()), "agent adapter install");
|
|
41833
42279
|
var repoArg = process.argv[2];
|
|
41834
42280
|
if (repoArg) {
|