looply-cli 0.5.6 → 0.5.8

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.
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProvisionBackoff = void 0;
3
4
  exports.nodeWorkspaceProvisioner = nodeWorkspaceProvisioner;
4
5
  exports.provisionOnce = provisionOnce;
5
6
  const node_os_1 = require("node:os");
@@ -67,25 +68,95 @@ function declaredCarry(cwd) {
67
68
  return [];
68
69
  return (0, config_1.parseConfig)((0, node_fs_1.readFileSync)(file, 'utf8')).run.carryIntoCheckouts ?? [];
69
70
  }
71
+ /**
72
+ * 🔴 A PROVISIONING THAT FAILS IS NOT TRIED AGAIN NEXT CADENCE — IT WAS, AND IT
73
+ * FILLED A DISK.
74
+ *
75
+ * "A failure is a line, tried again next cadence" was the whole policy, and a
76
+ * failure that is deterministic — the same carry refused the same way every
77
+ * time — then ran once a minute for as long as the daemon lived. MEASURED,
78
+ * 2026-10-16: Tabsy's daemon asked for one checkout for one goal every ~60 s,
79
+ * each attempt died in the carry (EPERM), and each left a ~0.7 GB worktree
80
+ * behind: 156 copies, 105 GB, C: at 0 bytes. `createWorkspace` now removes what
81
+ * a failed attempt made; this makes sure there are a handful of attempts a
82
+ * night rather than six hundred.
83
+ *
84
+ * ⚠️ ONE WAIT FOR THE WHOLE PROVISIONER, NOT ONE PER REQUEST. A provisioning
85
+ * that fails for one request fails for the next — it is the same carry from the
86
+ * same project — so a second request must not buy a fresh attempt. Doubling
87
+ * from `baseMs`, held at `capMs`; a success clears it. Built ONCE per daemon: a
88
+ * backoff made per cadence remembers nothing.
89
+ */
90
+ class ProvisionBackoff {
91
+ baseMs;
92
+ capMs;
93
+ failures = 0;
94
+ until = null;
95
+ constructor(baseMs = 60_000, capMs = 3_600_000) {
96
+ this.baseMs = baseMs;
97
+ this.capMs = capMs;
98
+ }
99
+ /** When the next attempt may be made, or null when it may be made now. */
100
+ waiting(now) {
101
+ return this.until !== null && now < this.until ? this.until : null;
102
+ }
103
+ /** Record a failure at `now`; answers when the next attempt may be made. */
104
+ failed(now) {
105
+ this.failures += 1;
106
+ const wait = Math.min(this.capMs, this.baseMs * 2 ** Math.min(this.failures - 1, 30));
107
+ this.until = now + wait;
108
+ return this.until;
109
+ }
110
+ succeeded() {
111
+ this.failures = 0;
112
+ this.until = null;
113
+ }
114
+ }
115
+ exports.ProvisionBackoff = ProvisionBackoff;
70
116
  /**
71
117
  * Answer the tick's request, if there was one and provisioning is on. The log
72
118
  * line names the checkout and the daemon's reason; a failure names itself.
73
119
  */
74
- async function provisionOnce(request, provisioner, log) {
120
+ async function provisionOnce(request, provisioner, log, guard = {}) {
75
121
  if (request === null)
76
122
  return { outcome: 'none', path: null };
77
123
  if (provisioner === null) {
78
124
  log(`daemon : the scheduler asked for a checkout ${request.because}, but provisioning is off — not created`);
79
125
  return { outcome: 'none', path: null };
80
126
  }
127
+ /**
128
+ * ⚠️ THE HOLD FIRST, AND A HOLD THAT CANNOT BE READ IS A HOLD — the caller's
129
+ * reader already answers so for an unreadable control file, and a reader that
130
+ * throws is treated the same way rather than as permission.
131
+ */
132
+ if (guard.held !== undefined) {
133
+ let held;
134
+ try {
135
+ held = await guard.held();
136
+ }
137
+ catch (e) {
138
+ held = `the hold could not be read: ${e.message}`;
139
+ }
140
+ if (held !== null) {
141
+ log(`daemon : the scheduler asked for a checkout ${request.because}, but this machine is held (${held}) — not created`);
142
+ return { outcome: 'held', path: null };
143
+ }
144
+ }
145
+ const now = guard.now ?? (() => Date.now());
146
+ // Silent inside the wait: the failure's own line already said when the next attempt is.
147
+ if (guard.backoff?.waiting(now()) != null)
148
+ return { outcome: 'deferred', path: null };
81
149
  try {
82
150
  const path = await provisioner();
151
+ guard.backoff?.succeeded();
83
152
  log(`daemon : provisioned workspace ${path} ${request.because}`);
84
153
  // The path comes back so the caller can keep the reaper off it this cadence (M4-4).
85
154
  return { outcome: 'provisioned', path };
86
155
  }
87
156
  catch (e) {
88
- log(`daemon : could not provision a workspace ${request.because} — ${e.message}`);
157
+ const next = guard.backoff?.failed(now());
158
+ const after = next === undefined ? '' : ` — not tried again until ${new Date(next).toISOString()}`;
159
+ log(`daemon : could not provision a workspace ${request.because} — ${e.message}${after}`);
89
160
  return { outcome: 'failed', path: null };
90
161
  }
91
162
  }
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-provisioner.js","sourceRoot":"","sources":["../src/workspace-provisioner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace-provisioner.js","sourceRoot":"","sources":["../src/workspace-provisioner.ts"],"names":[],"mappings":"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"}
@@ -62,6 +62,56 @@ export declare function forgetCreationMarker(cwd: string, name: string): boolean
62
62
  * the same identity rule `containerName` and `providerSessionDir` already use.
63
63
  */
64
64
  export declare function workspacesRoot(cwd: string, home?: string): string;
65
+ /**
66
+ * Give a new checkout the project's `.looply/config.yaml` when git did not.
67
+ *
68
+ * 🔴 A PROJECT THAT KEEPS `.looply/` OUT OF GIT GOT CHECKOUTS WITH NO CONFIG.
69
+ * Tabsy excludes `/.looply/` (`.git/info/exclude`), so `git worktree add` made a
70
+ * checkout without the file, and every Start there died at once — "config:
71
+ * cannot read …\.looply\config.yaml", three attempts, then human_required
72
+ * (2026-10-16, the first time parallel checkouts could be made at all).
73
+ *
74
+ * ⚠️ NEVER OVER A TRACKED ONE. If the checkout has the file, git put it there
75
+ * and it is what that commit says; this only fills the gap. A link is refused,
76
+ * for the recursive-delete reason in `checkout-carry.ts`.
77
+ */
78
+ export declare function carryProjectConfig(projectRoot: string, checkoutDir: string): boolean;
79
+ /** The machine-level settings file: `<home>/.looply/machine-settings.json`. */
80
+ export declare const MACHINE_SETTINGS_FILE = "machine-settings.json";
81
+ /** No new checkout is made when its drive has less free space than this (GB). */
82
+ export declare const DEFAULT_MIN_FREE_GB = 20;
83
+ /**
84
+ * `minFreeGb` from the machine settings, else {@link DEFAULT_MIN_FREE_GB}. Zero
85
+ * turns the check off; anything that is not a non-negative number throws, for
86
+ * the same reason as `workspacesBase`.
87
+ */
88
+ export declare function minFreeGb(home?: string): number;
89
+ /**
90
+ * Why no checkout may be made under `root` right now, or `null` when one may.
91
+ *
92
+ * 🔴 A CHECKOUT IS ~0.8 GB AND THE DRIVE WAS NOT ASKED. On 2026-10-16 checkouts
93
+ * kept being made until C: had 0 bytes, which also took down every other process
94
+ * on the machine. The answer is a sentence a person can act on, because it is
95
+ * what the run and the ticket say.
96
+ */
97
+ export declare function roomForCheckout(root: string, home?: string, freeBytes?: (dir: string) => number): string | null;
98
+ /**
99
+ * The directory every project's workspaces root sits under.
100
+ *
101
+ * 🔴 A CHECKOUT IS THE SIZE OF THE PROJECT PLUS ITS `node_modules` (Tabsy:
102
+ * ~0.8 GB each), and it always landed on the home drive. On 2026-10-16 that
103
+ * filled the owner's C: (about 100 GB of checkouts). `workspacesRoot` in
104
+ * `<home>/.looply/machine-settings.json` moves them to another drive, e.g.
105
+ * `{ "workspacesRoot": "D:\\looply-home\\workspaces" }`.
106
+ *
107
+ * ⚠️ READ FROM THE HOME, NEVER FROM THE REPOSITORY. `.looply/config.yaml` lives
108
+ * inside the worktree the model may edit, and this path is created, copied into
109
+ * and deleted recursively.
110
+ *
111
+ * ⚠️ AN UNUSABLE SETTING THROWS. Falling back to the home drive silently is the
112
+ * exact outcome the setting exists to prevent.
113
+ */
114
+ export declare function workspacesBase(home?: string): string;
65
115
  /**
66
116
  * One live execution, as the project can see it.
67
117
  *
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
package/dist/workspace.js CHANGED
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RunRegistry = exports.WorkspaceError = exports.nodeProbe = exports.WORKSPACES_DIR = void 0;
3
+ exports.RunRegistry = exports.WorkspaceError = exports.nodeProbe = exports.DEFAULT_MIN_FREE_GB = exports.MACHINE_SETTINGS_FILE = exports.WORKSPACES_DIR = void 0;
4
4
  exports.forgetCreationMarker = forgetCreationMarker;
5
5
  exports.workspacesRoot = workspacesRoot;
6
+ exports.carryProjectConfig = carryProjectConfig;
7
+ exports.minFreeGb = minFreeGb;
8
+ exports.roomForCheckout = roomForCheckout;
9
+ exports.workspacesBase = workspacesBase;
6
10
  exports.requestControlEverywhere = requestControlEverywhere;
7
11
  exports.createWorkspace = createWorkspace;
8
12
  exports.reapableCheckout = reapableCheckout;
@@ -128,7 +132,138 @@ function workspacesRoot(cwd, home = (0, node_os_1.homedir)()) {
128
132
  */
129
133
  const root = (0, trusted_git_1.repoIdentityPath)(cwd);
130
134
  const key = (0, node_crypto_1.createHash)('sha256').update(root.toLowerCase(), 'utf8').digest('hex').slice(0, 16);
131
- return (0, node_path_1.join)(home, '.looply', exports.WORKSPACES_DIR, key);
135
+ return (0, node_path_1.join)(workspacesBase(home), key);
136
+ }
137
+ /**
138
+ * Give a new checkout the project's `.looply/config.yaml` when git did not.
139
+ *
140
+ * 🔴 A PROJECT THAT KEEPS `.looply/` OUT OF GIT GOT CHECKOUTS WITH NO CONFIG.
141
+ * Tabsy excludes `/.looply/` (`.git/info/exclude`), so `git worktree add` made a
142
+ * checkout without the file, and every Start there died at once — "config:
143
+ * cannot read …\.looply\config.yaml", three attempts, then human_required
144
+ * (2026-10-16, the first time parallel checkouts could be made at all).
145
+ *
146
+ * ⚠️ NEVER OVER A TRACKED ONE. If the checkout has the file, git put it there
147
+ * and it is what that commit says; this only fills the gap. A link is refused,
148
+ * for the recursive-delete reason in `checkout-carry.ts`.
149
+ */
150
+ function carryProjectConfig(projectRoot, checkoutDir) {
151
+ const to = (0, node_path_1.join)(checkoutDir, git_driver_1.STATE_DIR, 'config.yaml');
152
+ if ((0, node_fs_1.existsSync)(to))
153
+ return false;
154
+ const from = (0, node_path_1.join)(projectRoot, git_driver_1.STATE_DIR, 'config.yaml');
155
+ if (!(0, node_fs_1.existsSync)(from))
156
+ return false;
157
+ if ((0, node_fs_1.lstatSync)(from).isSymbolicLink()) {
158
+ throw new Error(`${from} is a link; it is not copied into a checkout`);
159
+ }
160
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(to), { recursive: true });
161
+ (0, node_fs_1.cpSync)(from, to);
162
+ return true;
163
+ }
164
+ /** The machine-level settings file: `<home>/.looply/machine-settings.json`. */
165
+ exports.MACHINE_SETTINGS_FILE = 'machine-settings.json';
166
+ /** No new checkout is made when its drive has less free space than this (GB). */
167
+ exports.DEFAULT_MIN_FREE_GB = 20;
168
+ /**
169
+ * `minFreeGb` from the machine settings, else {@link DEFAULT_MIN_FREE_GB}. Zero
170
+ * turns the check off; anything that is not a non-negative number throws, for
171
+ * the same reason as `workspacesBase`.
172
+ */
173
+ function minFreeGb(home = (0, node_os_1.homedir)()) {
174
+ const file = (0, node_path_1.join)(home, '.looply', exports.MACHINE_SETTINGS_FILE);
175
+ let raw;
176
+ try {
177
+ raw = (0, node_fs_1.readFileSync)(file, 'utf8');
178
+ }
179
+ catch (e) {
180
+ if (e.code === 'ENOENT')
181
+ return exports.DEFAULT_MIN_FREE_GB;
182
+ throw e;
183
+ }
184
+ let value;
185
+ try {
186
+ value = JSON.parse(raw)?.minFreeGb;
187
+ }
188
+ catch {
189
+ throw new Error(`${file} is not valid JSON`);
190
+ }
191
+ if (value === undefined)
192
+ return exports.DEFAULT_MIN_FREE_GB;
193
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
194
+ throw new Error(`${file}: "minFreeGb" must be a number of GB, 0 or more`);
195
+ }
196
+ return value;
197
+ }
198
+ /**
199
+ * Why no checkout may be made under `root` right now, or `null` when one may.
200
+ *
201
+ * 🔴 A CHECKOUT IS ~0.8 GB AND THE DRIVE WAS NOT ASKED. On 2026-10-16 checkouts
202
+ * kept being made until C: had 0 bytes, which also took down every other process
203
+ * on the machine. The answer is a sentence a person can act on, because it is
204
+ * what the run and the ticket say.
205
+ */
206
+ function roomForCheckout(root, home = (0, node_os_1.homedir)(), freeBytes = (dir) => {
207
+ const s = (0, node_fs_1.statfsSync)(dir);
208
+ return Number(s.bavail) * Number(s.bsize);
209
+ }) {
210
+ const min = minFreeGb(home);
211
+ if (min === 0)
212
+ return null;
213
+ const free = freeBytes(root) / 1e9;
214
+ if (free >= min)
215
+ return null;
216
+ return (`not enough disk space for a new checkout: ${free.toFixed(1)} GB free where checkouts go (${root}), ` +
217
+ `and at least ${min} GB must stay free. Free some space, or move checkouts to another drive ` +
218
+ `("workspacesRoot" in ~/.looply/${exports.MACHINE_SETTINGS_FILE}).`);
219
+ }
220
+ /**
221
+ * The directory every project's workspaces root sits under.
222
+ *
223
+ * 🔴 A CHECKOUT IS THE SIZE OF THE PROJECT PLUS ITS `node_modules` (Tabsy:
224
+ * ~0.8 GB each), and it always landed on the home drive. On 2026-10-16 that
225
+ * filled the owner's C: (about 100 GB of checkouts). `workspacesRoot` in
226
+ * `<home>/.looply/machine-settings.json` moves them to another drive, e.g.
227
+ * `{ "workspacesRoot": "D:\\looply-home\\workspaces" }`.
228
+ *
229
+ * ⚠️ READ FROM THE HOME, NEVER FROM THE REPOSITORY. `.looply/config.yaml` lives
230
+ * inside the worktree the model may edit, and this path is created, copied into
231
+ * and deleted recursively.
232
+ *
233
+ * ⚠️ AN UNUSABLE SETTING THROWS. Falling back to the home drive silently is the
234
+ * exact outcome the setting exists to prevent.
235
+ */
236
+ function workspacesBase(home = (0, node_os_1.homedir)()) {
237
+ // `LOOPLY_WORKSPACES_ROOT` wins over the file, for a machine whose home is not writable or is shared.
238
+ const fromEnv = process.env.LOOPLY_WORKSPACES_ROOT;
239
+ if (fromEnv !== undefined && fromEnv !== '') {
240
+ if (!(0, node_path_1.isAbsolute)(fromEnv))
241
+ throw new Error('LOOPLY_WORKSPACES_ROOT must be an absolute path');
242
+ return fromEnv;
243
+ }
244
+ const file = (0, node_path_1.join)(home, '.looply', exports.MACHINE_SETTINGS_FILE);
245
+ let raw;
246
+ try {
247
+ raw = (0, node_fs_1.readFileSync)(file, 'utf8');
248
+ }
249
+ catch (e) {
250
+ if (e.code === 'ENOENT')
251
+ return (0, node_path_1.join)(home, '.looply', exports.WORKSPACES_DIR);
252
+ throw e;
253
+ }
254
+ let value;
255
+ try {
256
+ value = JSON.parse(raw)?.workspacesRoot;
257
+ }
258
+ catch {
259
+ throw new Error(`${file} is not valid JSON, so where workspaces go is unknown`);
260
+ }
261
+ if (value === undefined)
262
+ return (0, node_path_1.join)(home, '.looply', exports.WORKSPACES_DIR);
263
+ if (typeof value !== 'string' || !(0, node_path_1.isAbsolute)(value)) {
264
+ throw new Error(`${file}: "workspacesRoot" must be an absolute path`);
265
+ }
266
+ return value;
132
267
  }
133
268
  exports.nodeProbe = run_lease_1.nodeProbe;
134
269
  /**
@@ -1149,17 +1284,30 @@ carry = []) {
1149
1284
  if ((0, node_fs_1.existsSync)(path)) {
1150
1285
  throw new WorkspaceError(`a workspace already exists at ${path} — refusing to reuse it`);
1151
1286
  }
1287
+ const room = roomForCheckout(root, home);
1288
+ if (room !== null)
1289
+ throw new WorkspaceError(room);
1152
1290
  await git.addWorktree(path, branch, baseSha);
1153
1291
  /**
1154
1292
  * Immediately after the checkout and BEFORE the claim below, so a workspace is
1155
1293
  * never offered to a run in a state where its gate is guaranteed to fail.
1156
1294
  */
1157
- if (carry.length > 0) {
1295
+ {
1158
1296
  try {
1159
- (0, checkout_carry_1.carryIntoCheckout)(cwd, path, carry);
1297
+ carryProjectConfig(cwd, path);
1298
+ if (carry.length > 0)
1299
+ (0, checkout_carry_1.carryIntoCheckout)(cwd, path, carry);
1160
1300
  }
1161
1301
  catch (e) {
1162
- throw new WorkspaceError(`the workspace could not be provisioned: ${e.message}`);
1302
+ /**
1303
+ * 🔴 AND WHAT FAILED IS REMOVED, NOT LEFT FOR A REAPER. This threw with
1304
+ * the checkout still on disk, registered with git, on its own branch — and
1305
+ * with no marker and no record, so nothing but `--reap` would ever retire
1306
+ * it. MEASURED, 2026-10-16: the daemon retried every cadence and kept every
1307
+ * failure, 156 checkouts of ~0.7 GB each until C: had nothing left.
1308
+ */
1309
+ const kept = await discardFreshWorkspace(git, root, path, branch, baseSha);
1310
+ throw new WorkspaceError(`the workspace could not be provisioned: ${e.message}${kept}`);
1163
1311
  }
1164
1312
  }
1165
1313
  /**
@@ -1198,16 +1346,63 @@ carry = []) {
1198
1346
  * So it fails closed: the worktree this just created is removed and the
1199
1347
  * caller is told. A workspace that cannot be protected is not a workspace.
1200
1348
  */
1349
+ const kept = await discardFreshWorkspace(git, root, path, branch, baseSha);
1350
+ throw new WorkspaceError(`the workspace at ${path} could not be claimed for this run (${e.message}), so it was not kept${kept}`);
1351
+ }
1352
+ return path;
1353
+ }
1354
+ /**
1355
+ * Undo a `worktree add` this process made moments ago and nothing has used:
1356
+ * the directory, git's registration of it, and the branch cut for it. Answers
1357
+ * '' when all of it went, or a clause naming what is left.
1358
+ *
1359
+ * ⚠️ WHY A DIRECTORY DELETE IS ALLOWED HERE WHEN `removeWorktree` REFUSES TO
1360
+ * FORCE. `--force` is refused because it deletes ignored files a person may
1361
+ * value. This checkout is seconds old: everything in it is either git's own
1362
+ * checkout of a commit or what `carryIntoCheckout` copied into it — copies, with
1363
+ * every link left out — so there is nothing of anyone's to lose. Still only a
1364
+ * DIRECT child of the workspaces root, and only after git's own remove has had
1365
+ * the first say.
1366
+ *
1367
+ * ⚠️ THE BRANCH ONLY IF IT IS STILL WHERE IT WAS CUT (`deleteFreshBranch` is a
1368
+ * compare-and-delete), and never when `worktree add` itself failed — then the
1369
+ * branch may be somebody else's, which is why that call is outside this path.
1370
+ */
1371
+ async function discardFreshWorkspace(git, root, path, branch, baseSha) {
1372
+ const left = [];
1373
+ try {
1374
+ await git.removeWorktree(path);
1375
+ }
1376
+ catch {
1377
+ if ((0, node_fs_1.existsSync)(path) && (0, node_path_1.resolve)((0, node_path_1.dirname)(path)) === (0, node_path_1.resolve)(root)) {
1378
+ try {
1379
+ (0, node_fs_1.rmSync)(path, { recursive: true, force: true, maxRetries: 3 });
1380
+ }
1381
+ catch {
1382
+ // Said below.
1383
+ }
1384
+ }
1385
+ }
1386
+ try {
1387
+ await git.pruneWorktrees();
1388
+ }
1389
+ catch {
1390
+ left.push('its git registration');
1391
+ }
1392
+ // A checkout still on disk still has the branch checked out; deleting it would orphan that HEAD.
1393
+ if ((0, node_fs_1.existsSync)(path)) {
1394
+ left.push(`the directory ${path}`, `the branch ${branch}`);
1395
+ }
1396
+ else {
1201
1397
  try {
1202
- await git.removeWorktree(path);
1203
- await git.pruneWorktrees();
1398
+ if (!(await git.deleteFreshBranch(branch, baseSha)))
1399
+ left.push(`the branch ${branch}`);
1204
1400
  }
1205
1401
  catch {
1206
- // Leave it rather than force it. It is empty and `looply runs` lists it.
1402
+ left.push(`the branch ${branch}`);
1207
1403
  }
1208
- throw new WorkspaceError(`the workspace at ${path} could not be claimed for this run (${e.message}), so it was not kept`);
1209
1404
  }
1210
- return path;
1405
+ return left.length === 0 ? '' : ` (could not remove ${left.join(', ')} — \`looply runs --reap\` retires it)`;
1211
1406
  }
1212
1407
  /**
1213
1408
  * Whether a workspace may be reaped, and why not when it may not.