toolroll 0.9.90 → 0.9.91

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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.91 — 2026-11-04
4
+
5
+ - **A deploy checks your HTTPS console before it changes anything.** If the
6
+ console you reach Toolroll through (over Tailscale, say) doesn't answer, the
7
+ deploy now stops before it pauses new work or stops the running service,
8
+ and says to bring the console back. It used to find out only at the end,
9
+ with the new version already running and new work paused.
10
+ - **A release finishes a deploy that stopped after the new version started.**
11
+ If a deploy's last check failed once the new version was already running
12
+ (an HTTPS console that didn't answer, say), `toolroll release` sent you to
13
+ `toolroll release --recover-deploy`, which sent you back, and new work
14
+ stayed paused. Rerunning the release now finishes the deploy: it checks
15
+ the new service, both consoles and the project workers, then lifts the
16
+ pause.
17
+ - **A resumed release reads its task with its own build.** It used to call
18
+ whichever `toolroll` came first on your PATH, which until the release
19
+ relinks it is still the previous version and can't open a database the
20
+ deploy just upgraded.
21
+
3
22
  ## 0.9.90 — 2026-11-04
4
23
 
5
24
  - **Type `toolroll` and you're in.** At your own terminal, bare `toolroll`
@@ -62,6 +62,8 @@ export type AdapterOptions = {
62
62
  formula?: string;
63
63
  /** How long the deployment itself may run, in minutes. */
64
64
  deployMinutes?: number;
65
+ /** The Toolroll CLI the release calls, as [program, ...leading args]; by default this runtime's own bin.js. */
66
+ cli?: readonly string[];
65
67
  run?: Exec;
66
68
  fetch?: typeof fetch;
67
69
  say?: (line: string) => void;
@@ -10,6 +10,7 @@ import { existsSync, lstatSync, mkdtempSync, readdirSync, readFileSync, realpath
10
10
  import { homedir, tmpdir } from "node:os";
11
11
  import { basename, delimiter, dirname, join } from "node:path";
12
12
  import { createRequire } from "node:module";
13
+ import { fileURLToPath } from "node:url";
13
14
  import { parse as parseYaml } from "yaml";
14
15
  import { sleep } from "./sleep.js";
15
16
  import { callLabel, readOnlyCall, retryCall, retryingExec, writeWithRecheck } from "./release-net.js";
@@ -50,6 +51,13 @@ export const exec = (command, args, options = {}) => new Promise(done => {
50
51
  child.stdin?.end();
51
52
  });
52
53
  /** Without NODE_OPTIONS, which a test runner or debugger may have left for this process. */
54
+ /** This runtime's own CLI (dist/bin.js beside this module) when it has one; from source, `toolroll` as installed. */
55
+ const ownCli = () => {
56
+ const bin = join(dirname(fileURLToPath(import.meta.url)), "bin.js");
57
+ return existsSync(bin) ? [process.execPath, bin] : ["toolroll"];
58
+ };
59
+ /** Journal phases the deployment's finish accepts (scripts/deploy-phases.mjs finish): the new service runs, unproven. */
60
+ const FINISHES_FORWARD = Object.freeze(["started", "healthy"]);
53
61
  const cleanEnv = (extra = {}) => {
54
62
  const env = { ...process.env, ...extra };
55
63
  delete env["NODE_OPTIONS"];
@@ -244,8 +252,11 @@ export function releaseAdapters(options) {
244
252
  /** A write by gh, never retried by itself. */
245
253
  const ghWrite = (remainingMs, ...args) => run(tools.gh, args, { cwd: "/", env: gitEnv, timeoutMs: Math.min(120_000, remainingMs) });
246
254
  const ghJson = async (what, ...args) => JSON.parse(await must(gh(...args), what));
255
+ // The build running this release, never a PATH lookup: until the link step, `toolroll` on PATH is still the previous
256
+ // build, which refuses a database the deployment migrated, so a release resumed after its deploy couldn't read its task.
257
+ const cli = options.cli ?? ownCli();
247
258
  const toolroll = async (...args) => {
248
- const result = await run("toolroll", [...args, "--json"], { env: cleanEnv(), timeoutMs: 120_000 });
259
+ const result = await run(cli[0], [...cli.slice(1), ...args, "--json"], { env: cleanEnv(), timeoutMs: 120_000 });
249
260
  try {
250
261
  return object(JSON.parse(result.stdout));
251
262
  }
@@ -562,6 +573,24 @@ export function releaseAdapters(options) {
562
573
  const running = (options.processes ?? processes)().filter(one => one.command.includes("deploy-browser.mjs") && one.command.includes(stage));
563
574
  if (running.length > 0)
564
575
  return { ok: false, message: `the deployment is still running (process ${running.map(one => one.pid).join(", ")}); let it finish, then rerun` };
576
+ // The new service started (deploy-rescue.ts FORWARD_ONLY): recovery refuses to roll it back, so the release
577
+ // resumes it forward with the deployment's own finish (health, both consoles, leases, then the pause lifted).
578
+ // Finishing deploys, so the candidate is proven first, as for a deploy.
579
+ const phase = (() => { try {
580
+ return object(JSON.parse(readFileSync(join(stage, "deployment.json"), "utf8")))["phase"];
581
+ }
582
+ catch {
583
+ return undefined;
584
+ } })();
585
+ if (FINISHES_FORWARD.includes(String(phase))) {
586
+ const proven = proveCandidate(runId);
587
+ if (!proven.ok)
588
+ return stopped(stage, proven.message);
589
+ const finished = await deployScript(worktree, stage, runId, proven, ["--yes", "--phase", "finish"]);
590
+ if (finished.code === 0)
591
+ return { ok: true };
592
+ return stopped(stage, `${tail(finished)} (the deployment journal is at ${String(phase)}; finishing it forward stopped; ${join(stage, "deployment.json")})`);
593
+ }
565
594
  // Recovery needs no approval, but it runs only the recorded commit's own script, extracted by this runtime.
566
595
  const recorded = recordedCommit(runId);
567
596
  if (recorded === null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.90",
3
+ "version": "0.9.91",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"