@seanyao/roll 3.614.1 → 3.614.2
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/CHANGELOG.md +8 -0
- package/dist/roll.mjs +220 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v3.614.2 — 2026-06-14
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
|
|
9
|
+
- `roll loop go` 启动时会给清楚的反馈了:告诉你起的是哪个会话、在做哪些卡、第一轮起没起、怎么只读地看——之前只甩一句模糊的 "started" 就退出,你根本不知道开没开。会话复用时也会补上观察窗,attach 进去能直接看到实时流、而不是落在干活的窗口里。还能 `--attach` 直接在前台跟住实时流(此时 Ctrl-C 只停看、不停循环)。(FIX-289)
|
|
10
|
+
- 失败/空转的一轮循环现在也记下用了哪个模型、烧了多少 token、花了多少钱:之前账本那行只剩时间、其余全是 —;读不到用量时如实标"未知",不再假装成 0。而且每一轮结束(不管成没成)都会刷新项目网页,失败的循环不再在循环页里隐身。(FIX-290)
|
|
11
|
+
- 网页截图取证不再因为"本地没装 Chromium"就悄悄降级成纯文本:有图形界面时直接用系统截屏(打开浏览器截真窗口、真像素),没界面才用无头浏览器,都不行才如实记"没截成"——绝不拿文本冒充截图。(FIX-291)
|
|
12
|
+
|
|
5
13
|
## v3.614.1 — 2026-06-14
|
|
6
14
|
|
|
7
15
|
### 新功能
|
package/dist/roll.mjs
CHANGED
|
@@ -9452,6 +9452,10 @@ function terminalBoundsScript(title) {
|
|
|
9452
9452
|
"end tell"
|
|
9453
9453
|
].join("\n");
|
|
9454
9454
|
}
|
|
9455
|
+
async function hasGuiSession(run) {
|
|
9456
|
+
const gui = await run("launchctl", ["managername"]);
|
|
9457
|
+
return gui.code === 0 && gui.stdout.includes("Aqua");
|
|
9458
|
+
}
|
|
9455
9459
|
async function resolveWindowRect(title, run) {
|
|
9456
9460
|
const r = await run("osascript", ["-e", terminalBoundsScript(title)]);
|
|
9457
9461
|
if (r.code !== 0)
|
|
@@ -9464,6 +9468,89 @@ async function resolveWindowRect(title, run) {
|
|
|
9464
9468
|
return null;
|
|
9465
9469
|
return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
|
|
9466
9470
|
}
|
|
9471
|
+
function browserOpenScript(target, browserApp, r) {
|
|
9472
|
+
const app = appleScriptString(browserApp);
|
|
9473
|
+
const url = appleScriptString(target);
|
|
9474
|
+
return [
|
|
9475
|
+
`tell application "${app}"`,
|
|
9476
|
+
" activate",
|
|
9477
|
+
" make new window",
|
|
9478
|
+
" try",
|
|
9479
|
+
` set URL of active tab of front window to "${url}"`,
|
|
9480
|
+
" on error",
|
|
9481
|
+
" try",
|
|
9482
|
+
` set URL of front document to "${url}"`,
|
|
9483
|
+
" end try",
|
|
9484
|
+
" end try",
|
|
9485
|
+
` delay ${BROWSER_RENDER_DELAY_S}`,
|
|
9486
|
+
" try",
|
|
9487
|
+
` set bounds of front window to {${r.x}, ${r.y}, ${r.x + r.w}, ${r.y + r.h}}`,
|
|
9488
|
+
" end try",
|
|
9489
|
+
"end tell"
|
|
9490
|
+
].join("\n");
|
|
9491
|
+
}
|
|
9492
|
+
function browserBoundsScript(browserApp) {
|
|
9493
|
+
const app = appleScriptString(browserApp);
|
|
9494
|
+
return [
|
|
9495
|
+
`tell application "${app}"`,
|
|
9496
|
+
" try",
|
|
9497
|
+
" activate",
|
|
9498
|
+
" set index of front window to 1",
|
|
9499
|
+
" delay 0.3",
|
|
9500
|
+
" return bounds of front window",
|
|
9501
|
+
" on error",
|
|
9502
|
+
' return ""',
|
|
9503
|
+
" end try",
|
|
9504
|
+
"end tell"
|
|
9505
|
+
].join("\n");
|
|
9506
|
+
}
|
|
9507
|
+
function browserCloseScript(browserApp) {
|
|
9508
|
+
const app = appleScriptString(browserApp);
|
|
9509
|
+
return [
|
|
9510
|
+
`tell application "${app}"`,
|
|
9511
|
+
" try",
|
|
9512
|
+
" close front window",
|
|
9513
|
+
" end try",
|
|
9514
|
+
"end tell"
|
|
9515
|
+
].join("\n");
|
|
9516
|
+
}
|
|
9517
|
+
async function resolveBrowserRect(browserApp, run) {
|
|
9518
|
+
const r = await run("osascript", ["-e", browserBoundsScript(browserApp)]);
|
|
9519
|
+
if (r.code !== 0)
|
|
9520
|
+
return null;
|
|
9521
|
+
const parts = r.stdout.trim().split(",").map((part) => Number(part.trim()));
|
|
9522
|
+
if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n)))
|
|
9523
|
+
return null;
|
|
9524
|
+
const [x1, y1, x2, y2] = parts;
|
|
9525
|
+
if (x2 <= x1 || y2 <= y1)
|
|
9526
|
+
return null;
|
|
9527
|
+
return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
|
|
9528
|
+
}
|
|
9529
|
+
async function captureWebViaBrowser(req, run) {
|
|
9530
|
+
const browserApp = req.browser ?? DEFAULT_BROWSER;
|
|
9531
|
+
const rect = parseRegion(req.region ?? DEFAULT_REGION);
|
|
9532
|
+
if (rect === null)
|
|
9533
|
+
return "bad region";
|
|
9534
|
+
const target = req.url;
|
|
9535
|
+
const opened = await run("osascript", ["-e", browserOpenScript(target, browserApp, rect)]);
|
|
9536
|
+
if (opened.code !== 0)
|
|
9537
|
+
return `osascript ${browserApp} open failed`;
|
|
9538
|
+
const liveRect = await resolveBrowserRect(browserApp, run);
|
|
9539
|
+
if (liveRect === null) {
|
|
9540
|
+
await run("osascript", ["-e", browserCloseScript(browserApp)]);
|
|
9541
|
+
return "browser window not found \u2014 refusing a blind-region shot";
|
|
9542
|
+
}
|
|
9543
|
+
const front = await run("sh", ["-c", "lsappinfo info -only name $(lsappinfo front)"]);
|
|
9544
|
+
if (front.code !== 0 || !front.stdout.includes(browserApp)) {
|
|
9545
|
+
await run("osascript", ["-e", browserCloseScript(browserApp)]);
|
|
9546
|
+
return `${browserApp} not frontmost \u2014 refusing to shoot another app's pixels`;
|
|
9547
|
+
}
|
|
9548
|
+
const shot = await run("screencapture", ["-x", "-R", `${liveRect.x},${liveRect.y},${liveRect.w},${liveRect.h}`, req.out]);
|
|
9549
|
+
await run("osascript", ["-e", browserCloseScript(browserApp)]);
|
|
9550
|
+
if (shot.code !== 0)
|
|
9551
|
+
return "screencapture failed (screen-recording permission?)";
|
|
9552
|
+
return null;
|
|
9553
|
+
}
|
|
9467
9554
|
function terminalExitTabScript(title) {
|
|
9468
9555
|
const esc8 = appleScriptString(title);
|
|
9469
9556
|
return [
|
|
@@ -9550,9 +9637,17 @@ async function captureScreenshot(req, deps = {}) {
|
|
|
9550
9637
|
return skip("ROLL_ATTEST_NO_BROWSER=1");
|
|
9551
9638
|
if (req.url === void 0 || req.url === "")
|
|
9552
9639
|
return skip("no url");
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9640
|
+
if (platform3 === "darwin" && await hasGuiSession(run)) {
|
|
9641
|
+
const reason = await captureWebViaBrowser(req, run);
|
|
9642
|
+
if (reason !== null)
|
|
9643
|
+
return skip(`GUI browser capture: ${reason}`);
|
|
9644
|
+
} else {
|
|
9645
|
+
const r = await run("npx", ["-y", "playwright@latest", "screenshot", req.url, req.out]);
|
|
9646
|
+
if (r.code !== 0) {
|
|
9647
|
+
const why = platform3 === "darwin" ? "no GUI session" : "non-macOS host";
|
|
9648
|
+
return skip(`headless Chromium unavailable or capture failed (${why}; screencapture fallback not applicable)`);
|
|
9649
|
+
}
|
|
9650
|
+
}
|
|
9556
9651
|
} else if (req.kind === "mobile-ios") {
|
|
9557
9652
|
if (platform3 !== "darwin")
|
|
9558
9653
|
return skip("not macOS");
|
|
@@ -9578,8 +9673,7 @@ async function captureScreenshot(req, deps = {}) {
|
|
|
9578
9673
|
const rawLine = req.tmux !== void 0 && req.tmux !== "" ? `tmux attach -t ${req.tmux}` : req.command ?? "";
|
|
9579
9674
|
if (containsSecret(rawLine))
|
|
9580
9675
|
return skip("secret in capture command \u2014 redact & reshoot");
|
|
9581
|
-
|
|
9582
|
-
if (gui.code !== 0 || !gui.stdout.includes("Aqua"))
|
|
9676
|
+
if (!await hasGuiSession(run))
|
|
9583
9677
|
return skip("no GUI session");
|
|
9584
9678
|
const rect = parseRegion(req.region ?? DEFAULT_REGION);
|
|
9585
9679
|
if (rect === null)
|
|
@@ -9638,7 +9732,7 @@ async function captureAll(reqs, deps = {}) {
|
|
|
9638
9732
|
out3.push(await captureScreenshot(r, deps));
|
|
9639
9733
|
return out3;
|
|
9640
9734
|
}
|
|
9641
|
-
var execFileAsync7, defaultRun2, SAFE_STEM, DEFAULT_REGION, TERMINAL_DONE_TIMEOUT_MS, TERMINAL_DONE_POLL_MS;
|
|
9735
|
+
var execFileAsync7, defaultRun2, SAFE_STEM, DEFAULT_REGION, TERMINAL_DONE_TIMEOUT_MS, TERMINAL_DONE_POLL_MS, DEFAULT_BROWSER, BROWSER_RENDER_DELAY_S;
|
|
9642
9736
|
var init_screenshot = __esm({
|
|
9643
9737
|
"packages/infra/dist/screenshot.js"() {
|
|
9644
9738
|
"use strict";
|
|
@@ -9661,6 +9755,8 @@ var init_screenshot = __esm({
|
|
|
9661
9755
|
DEFAULT_REGION = "0,0,1280,800";
|
|
9662
9756
|
TERMINAL_DONE_TIMEOUT_MS = 12e4;
|
|
9663
9757
|
TERMINAL_DONE_POLL_MS = 100;
|
|
9758
|
+
DEFAULT_BROWSER = "Google Chrome";
|
|
9759
|
+
BROWSER_RENDER_DELAY_S = 2;
|
|
9664
9760
|
}
|
|
9665
9761
|
});
|
|
9666
9762
|
|
|
@@ -9684,6 +9780,9 @@ __export(dist_exports, {
|
|
|
9684
9780
|
attachArgv: () => attachArgv,
|
|
9685
9781
|
branchCreate: () => branchCreate,
|
|
9686
9782
|
branchDelete: () => branchDelete,
|
|
9783
|
+
browserBoundsScript: () => browserBoundsScript,
|
|
9784
|
+
browserCloseScript: () => browserCloseScript,
|
|
9785
|
+
browserOpenScript: () => browserOpenScript,
|
|
9687
9786
|
canonicalProjectPath: () => canonicalProjectPath,
|
|
9688
9787
|
captureAll: () => captureAll,
|
|
9689
9788
|
captureFromMarker: () => captureFromMarker,
|
|
@@ -17137,7 +17236,9 @@ function ledgerVerdict(status2, outcome) {
|
|
|
17137
17236
|
function ledgerFailedCount(rows) {
|
|
17138
17237
|
return rows.filter((r) => r.verdict === "failed" || r.verdict === "reverted" || r.verdict === "blocked").length;
|
|
17139
17238
|
}
|
|
17140
|
-
function fmtTokens2(tin, tout) {
|
|
17239
|
+
function fmtTokens2(tin, tout, usageUnknown) {
|
|
17240
|
+
if (usageUnknown)
|
|
17241
|
+
return "?";
|
|
17141
17242
|
const a = typeof tin === "number" ? tin : 0;
|
|
17142
17243
|
const b = typeof tout === "number" ? tout : 0;
|
|
17143
17244
|
if (a + b === 0)
|
|
@@ -17241,6 +17342,7 @@ function collectCycleLedger(projectPath3) {
|
|
|
17241
17342
|
const rawTs = row2["ts"];
|
|
17242
17343
|
const ts = typeof rawTs === "string" ? Date.parse(rawTs) : typeof rawTs === "number" ? rawTs > 1e10 ? rawTs : rawTs * 1e3 : Number.NaN;
|
|
17243
17344
|
const cost = typeof row2["cost_effective_usd"] === "number" ? row2["cost_effective_usd"] : typeof row2["cost_usd"] === "number" ? row2["cost_usd"] : void 0;
|
|
17345
|
+
const usageUnknown = row2["usage_unknown"] === true;
|
|
17244
17346
|
const ev = byCycle.get(cycleId);
|
|
17245
17347
|
const prNumber = storyId !== "" ? prMergedBy.get(storyId) : void 0;
|
|
17246
17348
|
const prOpen = storyId !== "" ? prOpenBy.get(storyId) : void 0;
|
|
@@ -17254,8 +17356,8 @@ function collectCycleLedger(projectPath3) {
|
|
|
17254
17356
|
storyId,
|
|
17255
17357
|
agent: String(row2["agent"] ?? ""),
|
|
17256
17358
|
model: typeof row2["model"] === "string" && row2["model"] !== "" ? row2["model"] : String(row2["agent"] ?? "\u2014") || "\u2014",
|
|
17257
|
-
tokens: fmtTokens2(row2["tokens_in"], row2["tokens_out"]),
|
|
17258
|
-
cost: cost !== void 0 ? `$${cost.toFixed(2)}` : "\u2014",
|
|
17359
|
+
tokens: fmtTokens2(row2["tokens_in"], row2["tokens_out"], usageUnknown),
|
|
17360
|
+
cost: cost !== void 0 ? `$${cost.toFixed(2)}` : usageUnknown ? "?" : "\u2014",
|
|
17259
17361
|
duration: fmtDuration(row2["duration_sec"]),
|
|
17260
17362
|
tape: rowTape(row2, verdict, ev, prNumber, prOpen),
|
|
17261
17363
|
evidence
|
|
@@ -23605,6 +23707,8 @@ function pad3(s, w) {
|
|
|
23605
23707
|
function tokensTotal(tokens) {
|
|
23606
23708
|
if (tokens === "\u2014")
|
|
23607
23709
|
return "\u2014";
|
|
23710
|
+
if (tokens === "?")
|
|
23711
|
+
return "?";
|
|
23608
23712
|
const parts = tokens.split("/");
|
|
23609
23713
|
const num4 = (p) => p.endsWith("k") ? Number(p.slice(0, -1)) * 1e3 : Number(p);
|
|
23610
23714
|
const total = parts.reduce((a, p) => a + (Number.isFinite(num4(p)) ? num4(p) : 0), 0);
|
|
@@ -24728,6 +24832,7 @@ function realDeps4() {
|
|
|
24728
24832
|
}
|
|
24729
24833
|
},
|
|
24730
24834
|
startTmux: startGoTmux,
|
|
24835
|
+
followFeed: followGoLiveFeed,
|
|
24731
24836
|
runOnce: realRunOnce,
|
|
24732
24837
|
readUsageLimits: readAnthropicUsageLimits,
|
|
24733
24838
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
@@ -24781,6 +24886,7 @@ function parseOptions2(args) {
|
|
|
24781
24886
|
let worker = false;
|
|
24782
24887
|
let noTmux = false;
|
|
24783
24888
|
let noWait = false;
|
|
24889
|
+
let attach = false;
|
|
24784
24890
|
let scopeSpecified = false;
|
|
24785
24891
|
let reviewMode = "auto";
|
|
24786
24892
|
let reviewModeSpecified = false;
|
|
@@ -24798,6 +24904,10 @@ function parseOptions2(args) {
|
|
|
24798
24904
|
noWait = true;
|
|
24799
24905
|
continue;
|
|
24800
24906
|
}
|
|
24907
|
+
if (arg === "--attach" || arg === "--follow") {
|
|
24908
|
+
attach = true;
|
|
24909
|
+
continue;
|
|
24910
|
+
}
|
|
24801
24911
|
if (arg === "--epic") {
|
|
24802
24912
|
const epic = args[i + 1]?.trim() ?? "";
|
|
24803
24913
|
if (epic !== "") {
|
|
@@ -24882,6 +24992,7 @@ function parseOptions2(args) {
|
|
|
24882
24992
|
worker,
|
|
24883
24993
|
noTmux,
|
|
24884
24994
|
noWait,
|
|
24995
|
+
attach,
|
|
24885
24996
|
scope,
|
|
24886
24997
|
scopeSpecified,
|
|
24887
24998
|
reviewMode,
|
|
@@ -24897,7 +25008,7 @@ function hasHelpArg2(args) {
|
|
|
24897
25008
|
}
|
|
24898
25009
|
function loopGoHelp() {
|
|
24899
25010
|
return [
|
|
24900
|
-
"Usage: roll loop go [--epic <name>|--cards <ids>] [--budget <usd>] [--for <duration>] [--max-cycles <n>] [--review <auto|hetero|self|off>] [--no-wait] [--no-tmux]",
|
|
25011
|
+
"Usage: roll loop go [--epic <name>|--cards <ids>] [--budget <usd>] [--for <duration>] [--max-cycles <n>] [--review <auto|hetero|self|off>] [--no-wait] [--attach] [--no-tmux]",
|
|
24901
25012
|
" Chain goal-mode cycles until the scoped backlog is complete, paused, budget-limited, or capped.",
|
|
24902
25013
|
" \u6309 goal \u8303\u56F4\u8FDE\u7EED\u6267\u884C cycle\uFF0C\u76F4\u5230\u5B8C\u6210\u3001\u6682\u505C\u3001\u9884\u7B97\u53D7\u9650\u6216\u8FBE\u5230\u4E0A\u9650\u3002",
|
|
24903
25014
|
"",
|
|
@@ -24910,6 +25021,7 @@ function loopGoHelp() {
|
|
|
24910
25021
|
" --usage-threshold <ratio> Pause when account usage reaches this ratio; default 0.85.",
|
|
24911
25022
|
" --no-wait Do not wait for usage windows to recover after a usage-limit pause.",
|
|
24912
25023
|
" --review <mode> Final review policy before completion: auto, hetero, self, or off.",
|
|
25024
|
+
" --attach, --follow Start the session, then follow the read-only live feed in the foreground (Ctrl-C stops the view, not the loop).",
|
|
24913
25025
|
" --no-tmux Run in the current process instead of starting a tmux session.",
|
|
24914
25026
|
"",
|
|
24915
25027
|
"Limits are explicit per run (FIX-279):",
|
|
@@ -25000,21 +25112,76 @@ function shellQuote3(value) {
|
|
|
25000
25112
|
function rollBin() {
|
|
25001
25113
|
return (process.env["ROLL_BIN"] ?? "").trim() || process.argv[1] || "roll";
|
|
25002
25114
|
}
|
|
25115
|
+
function goSessionName(slug) {
|
|
25116
|
+
return `roll-loop-${slug}`;
|
|
25117
|
+
}
|
|
25118
|
+
function watchCommand(projectPath3, slug, rollBin3) {
|
|
25119
|
+
const live = shellQuote3(join44(runtimeDir2(projectPath3), "live.log"));
|
|
25120
|
+
return `printf 'roll goal \xB7 ${slug}\\n'; tail -n +1 -F ${live} | ${shellQuote3(rollBin3)} loop fmt`;
|
|
25121
|
+
}
|
|
25122
|
+
function planGoTmuxCommands(input, state) {
|
|
25123
|
+
const session = goSessionName(input.slug);
|
|
25124
|
+
const watch = watchCommand(input.projectPath, input.slug, input.rollBin);
|
|
25125
|
+
const workerArgs = input.args.filter((arg) => arg !== "--worker" && arg !== "--no-tmux" && arg !== "--attach" && arg !== "--follow").concat("--worker").map(shellQuote3).join(" ");
|
|
25126
|
+
const command = `cd ${shellQuote3(input.projectPath)} && ROLL_LOOP_GO_WORKER=1 ROLL_LOOP_NO_TMUX=1 ROLL_BIN=${shellQuote3(input.rollBin)} ${shellQuote3(input.rollBin)} loop go ${workerArgs}`;
|
|
25127
|
+
const plan = [];
|
|
25128
|
+
if (!state.sessionExists) {
|
|
25129
|
+
plan.push(["new-session", "-d", "-s", session, "-x", "200", "-y", "50", "-n", "watch", watch]);
|
|
25130
|
+
} else if (!state.watchWindowExists) {
|
|
25131
|
+
plan.push(["new-window", "-d", "-t", session, "-n", "watch", watch]);
|
|
25132
|
+
}
|
|
25133
|
+
plan.push(["new-window", "-d", "-t", session, "-n", "go", command]);
|
|
25134
|
+
return plan;
|
|
25135
|
+
}
|
|
25136
|
+
function probeGoTmuxState(slug) {
|
|
25137
|
+
const session = goSessionName(slug);
|
|
25138
|
+
const sessionExists = spawnSync4("tmux", ["has-session", "-t", session], { stdio: "ignore" }).status === 0;
|
|
25139
|
+
if (!sessionExists)
|
|
25140
|
+
return { sessionExists: false, watchWindowExists: false };
|
|
25141
|
+
const listed = spawnSync4("tmux", ["list-windows", "-t", session, "-F", "#{window_name}"], { encoding: "utf8" });
|
|
25142
|
+
const windows = listed.status === 0 ? String(listed.stdout ?? "").split("\n").map((w) => w.trim()) : [];
|
|
25143
|
+
return { sessionExists: true, watchWindowExists: windows.includes("watch") };
|
|
25144
|
+
}
|
|
25003
25145
|
function startGoTmux(input) {
|
|
25004
|
-
const session =
|
|
25005
|
-
const rt = runtimeDir2(input.projectPath);
|
|
25006
|
-
const workerArgs = input.args.filter((arg) => arg !== "--worker" && arg !== "--no-tmux").concat("--worker").map(shellQuote3).join(" ");
|
|
25007
|
-
const watch = `printf 'roll goal \xB7 ${input.slug}\\n'; tail -n +1 -F ${shellQuote3(join44(rt, "live.log"))} | ${shellQuote3(input.rollBin)} loop fmt`;
|
|
25146
|
+
const session = goSessionName(input.slug);
|
|
25008
25147
|
try {
|
|
25009
|
-
|
|
25010
|
-
|
|
25148
|
+
const plan = planGoTmuxCommands(input, probeGoTmuxState(input.slug));
|
|
25149
|
+
let goStarted = false;
|
|
25150
|
+
for (const argv of plan) {
|
|
25151
|
+
const ok8 = spawnSync4("tmux", argv, { stdio: "ignore" }).status === 0;
|
|
25152
|
+
if (argv[0] === "new-window" && argv.includes("go") && argv[argv.length - 1]?.startsWith("cd "))
|
|
25153
|
+
goStarted = ok8;
|
|
25011
25154
|
}
|
|
25012
|
-
|
|
25013
|
-
return spawnSync4("tmux", ["new-window", "-d", "-t", session, "-n", "go", command], { stdio: "ignore" }).status === 0;
|
|
25155
|
+
return goStarted;
|
|
25014
25156
|
} catch {
|
|
25015
25157
|
return false;
|
|
25016
25158
|
}
|
|
25017
25159
|
}
|
|
25160
|
+
function followGoLiveFeed(projectPath3, rollBin3) {
|
|
25161
|
+
return new Promise((resolve5) => {
|
|
25162
|
+
const live = join44(runtimeDir2(projectPath3), "live.log");
|
|
25163
|
+
const tail3 = spawn3("tail", ["-n", "+1", "-F", live], { stdio: ["ignore", "pipe", "inherit"] });
|
|
25164
|
+
const fmt = spawn3(rollBin3.endsWith(".js") || rollBin3.endsWith(".mjs") ? process.execPath : rollBin3, rollBin3.endsWith(".js") || rollBin3.endsWith(".mjs") ? [rollBin3, "loop", "fmt"] : ["loop", "fmt"], { stdio: ["pipe", "inherit", "inherit"] });
|
|
25165
|
+
if (tail3.stdout !== null && fmt.stdin !== null)
|
|
25166
|
+
tail3.stdout.pipe(fmt.stdin);
|
|
25167
|
+
const finish = () => {
|
|
25168
|
+
try {
|
|
25169
|
+
tail3.kill("SIGTERM");
|
|
25170
|
+
} catch {
|
|
25171
|
+
}
|
|
25172
|
+
try {
|
|
25173
|
+
fmt.kill("SIGTERM");
|
|
25174
|
+
} catch {
|
|
25175
|
+
}
|
|
25176
|
+
process.removeListener("SIGINT", finish);
|
|
25177
|
+
resolve5();
|
|
25178
|
+
};
|
|
25179
|
+
process.on("SIGINT", finish);
|
|
25180
|
+
fmt.on("exit", finish);
|
|
25181
|
+
tail3.on("exit", finish);
|
|
25182
|
+
tail3.on("error", finish);
|
|
25183
|
+
});
|
|
25184
|
+
}
|
|
25018
25185
|
function realRunOnce(input) {
|
|
25019
25186
|
const bin = rollBin();
|
|
25020
25187
|
const cmd = bin.endsWith(".js") || bin.endsWith(".mjs") ? process.execPath : bin;
|
|
@@ -25756,6 +25923,28 @@ function installStopHandlers(onStop) {
|
|
|
25756
25923
|
process.off(signal2, handler);
|
|
25757
25924
|
};
|
|
25758
25925
|
}
|
|
25926
|
+
function describeScope(scope) {
|
|
25927
|
+
if (scope.kind === "cards")
|
|
25928
|
+
return `cards ${scope.cards.join(", ")}`;
|
|
25929
|
+
if (scope.kind === "epic")
|
|
25930
|
+
return `epic ${scope.epic}`;
|
|
25931
|
+
return "all Todo backlog cards";
|
|
25932
|
+
}
|
|
25933
|
+
function goStartupFeedback(slug, scope) {
|
|
25934
|
+
const session = goSessionName(slug);
|
|
25935
|
+
return [
|
|
25936
|
+
`Goal go session started: ${session}`,
|
|
25937
|
+
` scope: ${describeScope(scope)}`,
|
|
25938
|
+
" cycles: first cycle is running now; cycles chain until the scope is complete, paused, or capped.",
|
|
25939
|
+
` observe: tmux attach -t ${session} (read-only watch window; the 'go' window is the worker \u2014 do not Ctrl-C it)`,
|
|
25940
|
+
` or follow inline: roll loop go --attach`,
|
|
25941
|
+
"",
|
|
25942
|
+
`goal \u8FDE\u8DD1\u4F1A\u8BDD\u5DF2\u542F\u52A8: ${session}`,
|
|
25943
|
+
" \u7B2C\u4E00\u4E2A cycle \u6B63\u5728\u8FD0\u884C\uFF1B\u4F1A\u6301\u7EED\u8FDE\u8DD1\u76F4\u5230\u8303\u56F4\u5B8C\u6210\u3001\u6682\u505C\u6216\u8FBE\u4E0A\u9650\u3002",
|
|
25944
|
+
` \u89C2\u5BDF (\u53EA\u8BFB): tmux attach -t ${session} \u6216 roll loop go --attach`,
|
|
25945
|
+
""
|
|
25946
|
+
].join("\n");
|
|
25947
|
+
}
|
|
25759
25948
|
async function loopGoCommand(args, deps = realDeps4()) {
|
|
25760
25949
|
if (hasHelpArg2(args)) {
|
|
25761
25950
|
process.stdout.write(loopGoHelp());
|
|
@@ -25766,9 +25955,11 @@ async function loopGoCommand(args, deps = realDeps4()) {
|
|
|
25766
25955
|
if (!opts.worker && !opts.noTmux && deps.hasTmux()) {
|
|
25767
25956
|
const started = deps.startTmux({ projectPath: id.path, slug: id.slug, args, rollBin: rollBin() });
|
|
25768
25957
|
if (started) {
|
|
25769
|
-
process.stdout.write(
|
|
25770
|
-
|
|
25771
|
-
|
|
25958
|
+
process.stdout.write(goStartupFeedback(id.slug, opts.scope));
|
|
25959
|
+
if (opts.attach && deps.followFeed !== void 0) {
|
|
25960
|
+
process.stdout.write("Following live feed (Ctrl-C stops the view, not the loop) \u2026\n\u6B63\u5728\u8DDF\u968F\u5B9E\u65F6\u8F93\u51FA (Ctrl-C \u53EA\u505C\u6B62\u67E5\u770B\uFF0C\u4E0D\u4F1A\u505C\u6B62 loop) \u2026\n");
|
|
25961
|
+
await deps.followFeed(id.path, rollBin());
|
|
25962
|
+
}
|
|
25772
25963
|
return 0;
|
|
25773
25964
|
}
|
|
25774
25965
|
}
|
|
@@ -30414,6 +30605,7 @@ ${diffStat}`;
|
|
|
30414
30605
|
} else if (cmd.status === "idle" && (ctx.storyId ?? "") !== "") {
|
|
30415
30606
|
ports.backlog.markStatus?.(ports.repoCwd, ctx.storyId ?? "", STATUS_MARKER.todo);
|
|
30416
30607
|
}
|
|
30608
|
+
refreshAggregates(ports.repoCwd);
|
|
30417
30609
|
return {};
|
|
30418
30610
|
}
|
|
30419
30611
|
// _worktree_alert.
|
|
@@ -30456,16 +30648,22 @@ function buildRunRow(cmd, ctx, nowSec2) {
|
|
|
30456
30648
|
row2["duration_sec"] = dur;
|
|
30457
30649
|
}
|
|
30458
30650
|
}
|
|
30651
|
+
const routedModel = (ctx.model ?? "").trim() !== "" ? ctx.model : ctx.agent ?? "";
|
|
30652
|
+
if (routedModel !== "")
|
|
30653
|
+
row2["model"] = routedModel;
|
|
30459
30654
|
if (ctx.cost !== void 0) {
|
|
30460
30655
|
row2["cost_usd"] = ctx.cost.estimatedCost;
|
|
30461
30656
|
row2["cost_effective_usd"] = ctx.cost.effectiveCost;
|
|
30462
|
-
|
|
30657
|
+
if (ctx.cost.model !== "")
|
|
30658
|
+
row2["model"] = ctx.cost.model;
|
|
30463
30659
|
row2["tokens_in"] = ctx.cost.tokensIn;
|
|
30464
30660
|
row2["tokens_out"] = ctx.cost.tokensOut;
|
|
30465
30661
|
if (ctx.cost.cacheRead !== void 0)
|
|
30466
30662
|
row2["tokens_cache_read"] = ctx.cost.cacheRead;
|
|
30467
30663
|
if (ctx.cost.cacheWrite !== void 0)
|
|
30468
30664
|
row2["tokens_cache_write"] = ctx.cost.cacheWrite;
|
|
30665
|
+
} else {
|
|
30666
|
+
row2["usage_unknown"] = true;
|
|
30469
30667
|
}
|
|
30470
30668
|
return row2;
|
|
30471
30669
|
}
|