@outerlayer/cli 0.5.1 → 0.7.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +19 -0
  3. package/dist/{agent-setup-IFH3FB5X.js → agent-setup-TXLDY5RV.js} +7 -6
  4. package/dist/{build-PAEKVYVK.js → build-JF5UOWKJ.js} +2 -1
  5. package/dist/build-info.json +1 -1
  6. package/dist/{check-4Q33B5ZE.js → check-ASZL5BEO.js} +39 -23
  7. package/dist/chunk-2VO25HHO.js +75 -0
  8. package/dist/{chunk-XDDW4FRS.js → chunk-5FWNEQNZ.js} +3 -3
  9. package/dist/{chunk-CHLVJNTE.js → chunk-6IYLDODX.js} +18 -6
  10. package/dist/{chunk-W7UTFZDH.js → chunk-6QWV3YDJ.js} +2 -2
  11. package/dist/{chunk-EGABNHZG.js → chunk-6ZZHWY2Z.js} +149 -18
  12. package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
  13. package/dist/chunk-7FTNIAHS.js +211 -0
  14. package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
  15. package/dist/{chunk-F4NU4H3V.js → chunk-AII7CKTH.js} +133 -35
  16. package/dist/{chunk-ZXBITXTS.js → chunk-AIW6GLET.js} +1 -1
  17. package/dist/{chunk-UWH5DSUP.js → chunk-AJKEHABI.js} +507 -90
  18. package/dist/{chunk-RTXKYLCA.js → chunk-AQ54MKSQ.js} +2 -2
  19. package/dist/{chunk-Z62HNKEJ.js → chunk-CEBIA5FD.js} +31 -5
  20. package/dist/{chunk-EX6M5JPR.js → chunk-D6FXM5OW.js} +6302 -28611
  21. package/dist/{chunk-RMAZ2NWA.js → chunk-DGAIVMMN.js} +23 -2
  22. package/dist/{chunk-3DD6AOWC.js → chunk-DTXOJDEH.js} +26 -2
  23. package/dist/{chunk-T52PKE2Y.js → chunk-E4PIFIC3.js} +244 -17
  24. package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
  25. package/dist/chunk-FBTE2A2X.js +38 -0
  26. package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
  27. package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
  28. package/dist/chunk-HKMPVPO7.js +55 -0
  29. package/dist/{chunk-UFFXNXLQ.js → chunk-JPR2WHHF.js} +31 -29
  30. package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
  31. package/dist/{chunk-H4CK2YK4.js → chunk-MQPRB5K7.js} +18 -10
  32. package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
  33. package/dist/chunk-OUUINEYB.js +27 -0
  34. package/dist/chunk-PUN7LMNS.js +39 -0
  35. package/dist/{install-cmd-7WXOIFDO.js → chunk-Q5G2ODUZ.js} +1 -2
  36. package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
  37. package/dist/{chunk-OR6AEEDO.js → chunk-SSU747O6.js} +85 -25
  38. package/dist/chunk-T6OIJQRT.js +31 -0
  39. package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
  40. package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
  41. package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
  42. package/dist/{chunk-JLDZTG3A.js → chunk-X2FSFU4C.js} +4 -1
  43. package/dist/chunk-YNJEOGDL.js +22434 -0
  44. package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
  45. package/dist/{chunk-WOBNRVQT.js → chunk-ZYHKJKSU.js} +1 -1
  46. package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
  47. package/dist/{cli-LHSUYYMI.js → cli-LEVCGAQV.js} +154 -79
  48. package/dist/command-FLSYCGIB.js +187 -0
  49. package/dist/config-I3TVFYLH.js +10 -0
  50. package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-MJMUONDY.js} +4 -4
  51. package/dist/{context-adopt-UMA4O5NY.js → context-adopt-CE4KDXS3.js} +5 -4
  52. package/dist/{context-materialize-G2FUFC72.js → context-materialize-QL33HDJN.js} +5 -4
  53. package/dist/{dist-WDV7U7UJ.js → dist-PXBSA7QX.js} +2 -1
  54. package/dist/{doctor-MOA3RIEP.js → doctor-FVDT3KJZ.js} +46 -19
  55. package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-7RPMRSVP.js} +13 -7
  56. package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
  57. package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-MKI7WETO.js} +12 -3
  58. package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-RMLGZASB.js} +14 -3
  59. package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-MTXAZ2GV.js} +15 -9
  60. package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
  61. package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
  62. package/dist/{hook-fast-EYENPK6F.js → hook-fast-XBOZ3N6L.js} +1 -1
  63. package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
  64. package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
  65. package/dist/index.js +3 -2
  66. package/dist/{init-FYDJAKTM.js → init-BACXESVV.js} +21 -14
  67. package/dist/{init-2IPOIXJV.js → init-VLW7H6RW.js} +40 -62
  68. package/dist/{init-cmd-KLIDRVXQ.js → init-cmd-VJFB5L7X.js} +10 -9
  69. package/dist/inspect-XAUSB7L3.js +63 -0
  70. package/dist/install-NMAPLLBQ.js +14 -0
  71. package/dist/install-cmd-B5SEU3G7.js +8 -0
  72. package/dist/lima-7NPCNGWT.js +6 -0
  73. package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
  74. package/dist/loop-D3243JKT.js +64 -0
  75. package/dist/{machine-ECU3QCZD.js → machine-EB3GEVI4.js} +20 -13
  76. package/dist/session-sender-LMACEJJS.js +29 -0
  77. package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
  78. package/dist/{status-CLATHCTQ.js → status-F4THTJJK.js} +36 -16
  79. package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-M6JA3OEE.js} +7 -7
  80. package/dist/{watch-3YW6Z3DQ.js → watch-NB5TGPEZ.js} +7 -7
  81. package/dist/{work-claim-cmd-3OPWA77E.js → work-claim-cmd-UHMWOS3G.js} +14 -7
  82. package/dist/{work-cmd-NUR4CAG6.js → work-cmd-6UMCFTAW.js} +9 -3
  83. package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-E4LEUIXZ.js} +9 -3
  84. package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
  85. package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-OPZWGSEQ.js} +10 -4
  86. package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-IDIAFFRU.js} +9 -2
  87. package/package.json +2 -1
  88. package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
  89. package/dist/chunk-D2KWEAN6.js +0 -55
  90. package/dist/chunk-ZOC7JSYC.js +0 -83
  91. package/dist/config-HL2AIR3K.js +0 -9
  92. package/dist/loop-NEBUGD2Q.js +0 -45
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
+ import { autoConnect } from './chunk-PUN7LMNS.js';
3
4
  import { detectActiveSession } from './chunk-BFESLKPP.js';
4
- import { sanitizeTerminal } from './chunk-WOBNRVQT.js';
5
- import { resolveRepoIdentity } from './chunk-EX6M5JPR.js';
5
+ import { sanitizeTerminal } from './chunk-ZYHKJKSU.js';
6
+ import { resolveRepoIdentity } from './chunk-D6FXM5OW.js';
6
7
  import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
7
8
  import { defaultFetch } from './chunk-BCLJSQCV.js';
8
9
  import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
@@ -79,6 +80,10 @@ function resolveCredentials(opts) {
79
80
  if (!resolved.ok) throw new WorkCommandError(resolved.message);
80
81
  return { url: resolved.url, apiKey: resolved.credential, appId: resolved.appId };
81
82
  }
83
+ async function connectIfOneFits(opts) {
84
+ const refusal = await autoConnect(opts);
85
+ if (refusal !== void 0) throw new WorkCommandError(refusal);
86
+ }
82
87
  function resolveRepository(repoFlag, cwd) {
83
88
  if (repoFlag) return repoFlag;
84
89
  const identity = resolveRepoIdentity(cwd);
@@ -299,6 +304,7 @@ async function runWorkBuild(opts) {
299
304
  const cwd = opts.cwd ?? process.cwd();
300
305
  const env = opts.env ?? process.env;
301
306
  const home = opts.home ?? homeFromEnv(env);
307
+ await connectIfOneFits(opts);
302
308
  const { url, apiKey, appId } = resolveCredentials(opts);
303
309
  const fetchImpl = opts.fetchImpl ?? defaultFetch;
304
310
  const facts = environmentFacts({ cwd, env, home, sessionIdOverride: opts.sessionId });
@@ -337,6 +343,18 @@ async function runWorkBuild(opts) {
337
343
  return { data, output };
338
344
  }
339
345
  async function runWorkLinkSession(opts) {
346
+ try {
347
+ return await linkSession(opts);
348
+ } catch (err) {
349
+ if (opts.logFailures) {
350
+ const { logHookError } = await import('./hook-fast-XBOZ3N6L.js');
351
+ const reason = err instanceof Error ? err.message : String(err);
352
+ logHookError(opts.home ?? homeFromEnv(opts.env ?? process.env), new Error(`work link-session #${opts.number} failed: ${reason}`));
353
+ }
354
+ throw err;
355
+ }
356
+ }
357
+ async function linkSession(opts) {
340
358
  const cwd = opts.cwd ?? process.cwd();
341
359
  const env = opts.env ?? process.env;
342
360
  const home = opts.home ?? homeFromEnv(env);
@@ -377,6 +395,7 @@ async function runWorkRemove(opts) {
377
395
  const cwd = opts.cwd ?? process.cwd();
378
396
  const env = opts.env ?? process.env;
379
397
  const home = opts.home ?? homeFromEnv(env);
398
+ await connectIfOneFits(opts);
380
399
  const creds = resolveCredentials(opts);
381
400
  if (!opts.reason) {
382
401
  throw new WorkCommandError("--reason is required \u2014 a removal records why");
@@ -420,6 +439,7 @@ async function runWorkRemove(opts) {
420
439
  }
421
440
  async function runWorkStatus(opts) {
422
441
  const cwd = opts.cwd ?? process.cwd();
442
+ await connectIfOneFits(opts);
423
443
  const creds = resolveCredentials(opts);
424
444
  const fetchByItem = opts.item !== void 0 ? parsePositiveInt(opts.item, "--item") : void 0;
425
445
  if (fetchByItem !== void 0) {
@@ -449,6 +469,7 @@ async function runWorkStatus(opts) {
449
469
  }
450
470
  async function runWorkList(opts) {
451
471
  const cwd = opts.cwd ?? process.cwd();
472
+ await connectIfOneFits(opts);
452
473
  const { url, apiKey, appId } = resolveCredentials(opts);
453
474
  const fetchImpl = opts.fetchImpl ?? defaultFetch;
454
475
  const repository = opts.repo ?? resolveRepoIdentity(cwd).gitRepo;
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { parseSize, MAX_ALLOW_HOSTS, allowHostProblem } from './chunk-F6GFZXZ3.js';
4
- import { isRefusedInConfig, resolveDestinations, isReservedVariable } from './chunk-AUFG23AA.js';
4
+ import { isRefusedInConfig, resolveDestinations, isReservedVariable } from './chunk-OD2VTIHN.js';
5
5
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
6
6
  import { existsSync, statSync, accessSync, constants, readFileSync } from 'fs';
7
7
  import { hostname } from 'os';
@@ -30,6 +30,9 @@ var DEFAULT_CONCURRENCY = 1;
30
30
  var DEFAULT_POLL_SECONDS = 60;
31
31
  var DEFAULT_TIME_LIMIT_MINUTES = 480;
32
32
  var DEFAULT_IDLE_LIMIT_MINUTES = 30;
33
+ var DEFAULT_CHECK_TIME_LIMIT_MINUTES = 10;
34
+ var CHECKS_KEYS = ["timeLimitMinutes"];
35
+ var MAX_CHECK_TIME_LIMIT_MINUTES = 240;
33
36
  var DEFAULT_BUILD = { memory: "8g", cpus: 4, pids: 4096, disk: "40g", variables: [] };
34
37
  var BUILD_KEYS = ["memory", "cpus", "pids", "disk", "variables", "destinations", "allowHosts"];
35
38
  var HOUSEKEEPING_KEYS = ["jobsKeepDays", "buildCacheMax", "minFreeDisk"];
@@ -81,6 +84,7 @@ var RUNNER_KEYS = [
81
84
  "hooks",
82
85
  "build",
83
86
  "housekeeping",
87
+ "checks",
84
88
  "autoUpdate"
85
89
  ];
86
90
  function runnerNumbers() {
@@ -199,6 +203,7 @@ function loadRunnerConfig(configPath) {
199
203
  checkBuiltinHooks(configPath, runner.hooks);
200
204
  const build = resolveBuild(configPath, runner.build);
201
205
  const housekeeping = resolveHousekeeping(configPath, runner.housekeeping, build.disk);
206
+ const checks = resolveChecks(configPath, runner.checks);
202
207
  if (config.scrub?.literals !== void 0 && !Array.isArray(config.scrub.literals)) {
203
208
  fail(configPath, "scrub.literals", "must be a list of strings");
204
209
  }
@@ -226,6 +231,7 @@ function loadRunnerConfig(configPath) {
226
231
  },
227
232
  build,
228
233
  housekeeping,
234
+ checks,
229
235
  // A host with executables of its own for provision and cleanup chose
230
236
  // its own environment, and an update could change what its hooks run
231
237
  // under; it opts in. The built-in hooks are the runner's own. Provision
@@ -275,6 +281,24 @@ function resolveHousekeeping(configPath, block, buildDisk) {
275
281
  if (minFreeDisk === void 0) fail(configPath, "runner.housekeeping.minFreeDisk", 'must be a size such as "40g"');
276
282
  return { jobsKeepDays: keys.jobsKeepDays ?? DEFAULT_JOBS_KEEP_DAYS, buildCacheMax, minFreeDisk };
277
283
  }
284
+ function resolveChecks(configPath, block) {
285
+ const given = block ?? {};
286
+ if (typeof given !== "object" || Array.isArray(given)) fail(configPath, "runner.checks", "must be an object");
287
+ const keys = given;
288
+ for (const key of Object.keys(keys)) {
289
+ if (CHECKS_KEYS.includes(key)) continue;
290
+ const near = nearestKey(key, CHECKS_KEYS);
291
+ fail(configPath, `runner.checks.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
292
+ }
293
+ const limit = keys.timeLimitMinutes === void 0 ? DEFAULT_CHECK_TIME_LIMIT_MINUTES : keys.timeLimitMinutes;
294
+ if (!Number.isInteger(limit) || limit < 1) {
295
+ fail(configPath, "runner.checks.timeLimitMinutes", "must be a whole number of at least 1");
296
+ }
297
+ if (limit > MAX_CHECK_TIME_LIMIT_MINUTES) {
298
+ fail(configPath, "runner.checks.timeLimitMinutes", `must be at most ${MAX_CHECK_TIME_LIMIT_MINUTES}`);
299
+ }
300
+ return { timeLimitMinutes: limit };
301
+ }
278
302
  function wholeBytes(bytes) {
279
303
  if (bytes === void 0) return void 0;
280
304
  const whole = Math.floor(bytes);
@@ -334,4 +358,4 @@ function resolveBuild(configPath, block) {
334
358
  };
335
359
  }
336
360
 
337
- export { RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, runnerVmOf };
361
+ export { DEFAULT_CHECK_TIME_LIMIT_MINUTES, RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, runnerVmOf };
@@ -1,29 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { readDefaultBranchHead, cloneRepository, buildRepositoryImage, CLAUDE_CODE_VERSION, firecrackerArch, MICROVM_ARCHES, DEFAULT_BASE_IMAGE, GUEST_HELPER_SHA256, GUEST_ASSETS_BASE_URL, GUEST_KERNEL_SHA256, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_VERSION } from './chunk-Z62HNKEJ.js';
3
+ import { readDefaultBranchHead, cloneRepository, buildRepositoryImage, CLAUDE_CODE_VERSION, firecrackerArch, MICROVM_ARCHES, DEFAULT_BASE_IMAGE, GUEST_HELPER_SHA256, GUEST_ASSETS_BASE_URL, GUEST_KERNEL_SHA256, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_VERSION } from './chunk-CEBIA5FD.js';
4
4
  import { startTimeOf, isSameProcess } from './chunk-N5FOE5PS.js';
5
5
  import { currentCliBuild } from './chunk-OGFGZQA3.js';
6
6
  import { createDockerHost, childEnvironment, spawnRun } from './chunk-774HEQL6.js';
7
7
  import { RecipeError } from './chunk-QOZKPLVJ.js';
8
- import { execCapture } from './chunk-W4SQCOZU.js';
9
- import { GUEST_CID } from './chunk-B2G7JHHB.js';
10
- import { WorkCommandError, postJson } from './chunk-RMAZ2NWA.js';
8
+ import { execCapture } from './chunk-ZOAEPWGL.js';
9
+ import { GUEST_CID } from './chunk-R7STSBLC.js';
10
+ import { WorkCommandError, postJson } from './chunk-DGAIVMMN.js';
11
11
  import { parseDuKilobytes, hostAllowed } from './chunk-F6GFZXZ3.js';
12
- import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, AGENT_CREDENTIAL_MISSING, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, DESTINATION_BASE_PORT } from './chunk-AUFG23AA.js';
13
- import { defaultGitRunner, materializeContext } from './chunk-XDDW4FRS.js';
14
- import { readContextManifest, readSourceCache } from './chunk-WIOZAJ2W.js';
12
+ import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, checkEnvironment, AGENT_CREDENTIAL_MISSING, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, DESTINATION_BASE_PORT } from './chunk-OD2VTIHN.js';
13
+ import { defaultGitRunner, materializeContext } from './chunk-5FWNEQNZ.js';
14
+ import { readContextManifest, readSourceCache } from './chunk-ZZXLM64C.js';
15
15
  import { signingFetchFor } from './chunk-BCLJSQCV.js';
16
16
  import { signerFor } from './chunk-M5POMKKW.js';
17
17
  import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir, runnerDir, RUNNER_DIR_MODE, vmAssetsDir } from './chunk-ZM2IMMYN.js';
18
18
  import { relative, join, dirname, resolve, basename } from 'path';
19
+ import { readFileSync, existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, accessSync, constants, readdirSync, statSync, rmdirSync, realpathSync, renameSync, lstatSync, unlinkSync, openSync, closeSync, mkdtempSync, createWriteStream, createReadStream } from 'fs';
19
20
  import { spawnSync, spawn } from 'child_process';
20
21
  import { createHash, randomBytes } from 'crypto';
21
- import { existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, realpathSync, renameSync, lstatSync, readFileSync, unlinkSync, readdirSync, openSync, closeSync, mkdtempSync, createWriteStream, statSync, createReadStream } from 'fs';
22
22
  import { createServer, request } from 'http';
23
23
  import { request as request$1 } from 'https';
24
24
  import { Transform } from 'stream';
25
25
  import { pipeline, finished } from 'stream/promises';
26
- import { createGunzip, gunzipSync, constants } from 'zlib';
26
+ import { createGunzip, gunzipSync, constants as constants$1 } from 'zlib';
27
27
  import { isIP, connect, createServer as createServer$1 } from 'net';
28
28
  import { lookup } from 'dns/promises';
29
29
  import { networkInterfaces } from 'os';
@@ -255,6 +255,116 @@ var BuildTokens = class {
255
255
  );
256
256
  }
257
257
  };
258
+ var BUILD_PREFIX = "build-";
259
+ var LEAF = "runner";
260
+ function serviceDirectory(selfCgroup, mount) {
261
+ const line = selfCgroup?.split("\n").find((l) => l.startsWith("0::"));
262
+ if (line === void 0) return void 0;
263
+ const path = line.slice(3).trim();
264
+ if (!path.endsWith(".service")) return void 0;
265
+ return join(mount, path);
266
+ }
267
+ function prepareRunnerCgroup(setup) {
268
+ if (setup.invocationId === void 0 || setup.invocationId === "") return void 0;
269
+ const root = serviceDirectory(setup.selfCgroup, setup.mount);
270
+ if (root === void 0) return void 0;
271
+ const { fs } = setup;
272
+ if (!fs.exists(root) || !fs.writable(join(root, "cgroup.procs"))) return void 0;
273
+ if (!(fs.read(join(root, "cgroup.controllers")) ?? "").split(/\s+/).includes("memory")) return void 0;
274
+ for (const name of fs.list(root)) {
275
+ if (!name.startsWith(BUILD_PREFIX)) continue;
276
+ try {
277
+ fs.rmdir(join(root, name));
278
+ } catch {
279
+ }
280
+ }
281
+ if (!fs.exists(join(root, LEAF))) fs.mkdir(join(root, LEAF));
282
+ fs.write(join(root, LEAF, "cgroup.procs"), String(setup.pid));
283
+ fs.write(join(root, "cgroup.subtree_control"), "+memory");
284
+ return { root };
285
+ }
286
+ var BUILD_RECLAIM_HEADROOM_BYTES = 256 * 1024 ** 2;
287
+ var BUILD_KILL_HEADROOM_BYTES = 1024 ** 3;
288
+ function createBuildCgroup(fs, root, buildId, buildMemoryBytes) {
289
+ const path = join(root, `${BUILD_PREFIX}${buildId}`);
290
+ fs.mkdir(path);
291
+ try {
292
+ fs.write(join(path, "memory.high"), String(buildMemoryBytes + BUILD_RECLAIM_HEADROOM_BYTES));
293
+ fs.write(join(path, "memory.max"), String(buildMemoryBytes + BUILD_KILL_HEADROOM_BYTES));
294
+ fs.write(join(path, "memory.swap.max"), "0");
295
+ } catch (err) {
296
+ removeBuildCgroup(fs, path);
297
+ throw err;
298
+ }
299
+ return path;
300
+ }
301
+ function outOfMemoryReason(memory) {
302
+ return `the build ran out of memory: the kernel killed its microVM at the limit of runner.build.memory (${memory ?? "8g"}) plus 1 GiB for Firecracker and its disks' page cache. Raise runner.build.memory, or lower runner.concurrency`;
303
+ }
304
+ function oomKills(fs, path) {
305
+ const match = /^oom_kill (\d+)$/m.exec(fs.read(join(path, "memory.events")) ?? "");
306
+ return match === null ? 0 : Number(match[1]);
307
+ }
308
+ function removeBuildCgroup(fs, path) {
309
+ try {
310
+ fs.rmdir(path);
311
+ } catch (err) {
312
+ if (err.code !== "ENOENT") throw err;
313
+ }
314
+ }
315
+ function realCgroupFs() {
316
+ return {
317
+ exists: (path) => existsSync(path),
318
+ read: (path) => {
319
+ try {
320
+ return readFileSync(path, "utf8");
321
+ } catch {
322
+ return void 0;
323
+ }
324
+ },
325
+ write: (path, data) => writeFileSync(path, data),
326
+ mkdir: (path) => mkdirSync(path),
327
+ rmdir: (path) => rmdirSync(path),
328
+ list: (path) => readdirSync(path).filter((name) => statSync(join(path, name)).isDirectory()),
329
+ writable: (path) => {
330
+ try {
331
+ accessSync(path, constants.W_OK);
332
+ return true;
333
+ } catch {
334
+ return false;
335
+ }
336
+ }
337
+ };
338
+ }
339
+ var delegatedRoot;
340
+ function setUpRunnerCgroup(over = {}) {
341
+ let selfCgroup;
342
+ try {
343
+ selfCgroup = readFileSync("/proc/self/cgroup", "utf8");
344
+ } catch {
345
+ selfCgroup = void 0;
346
+ }
347
+ let refusal;
348
+ try {
349
+ const prepared = prepareRunnerCgroup({ fs: realCgroupFs(), selfCgroup, pid: process.pid, invocationId: process.env.INVOCATION_ID, mount: "/sys/fs/cgroup", ...over });
350
+ delegatedRoot = prepared?.root;
351
+ } catch (err) {
352
+ delegatedRoot = void 0;
353
+ refusal = err instanceof Error ? err.message : String(err);
354
+ }
355
+ return { delegated: delegatedRoot !== void 0, ...refusal === void 0 ? {} : { refusal } };
356
+ }
357
+ function realCgroupPort() {
358
+ const fs = realCgroupFs();
359
+ return {
360
+ create: (buildId, bytes) => {
361
+ if (delegatedRoot === void 0) return void 0;
362
+ return createBuildCgroup(fs, delegatedRoot, buildId, bytes);
363
+ },
364
+ oomKills: (path) => oomKills(fs, path),
365
+ remove: (path) => removeBuildCgroup(fs, path)
366
+ };
367
+ }
258
368
  var BranchPrepareError = class extends Error {
259
369
  };
260
370
  var OWN_PREFIX = "outerlayer/";
@@ -431,12 +541,12 @@ function containerRunArgs(input) {
431
541
  "--forward",
432
542
  `${DESTINATION_BASE_PORT + index}=${JOB_MOUNT}/dest-${index}.sock`
433
543
  ]).flat(),
434
- ...[
544
+ ...input.builtIns === true ? [
435
545
  "--forward",
436
546
  `${GATEWAY_PORT}=${JOB_MOUNT}/${GATEWAY_SOCKET}`,
437
547
  "--forward",
438
548
  `${CLAUDE_PORT}=${JOB_MOUNT}/${CLAUDE_SOCKET}`
439
- ]
549
+ ] : []
440
550
  ];
441
551
  }
442
552
  function prepareRunArgs(input) {
@@ -747,7 +857,9 @@ function machineConfig(limits2, paths) {
747
857
  };
748
858
  }
749
859
  function firecrackerCommand(input) {
750
- return { command: input.binary, args: ["--no-api", "--config-file", input.configPath] };
860
+ const args = ["--no-api", "--config-file", input.configPath];
861
+ if (input.cgroup === void 0) return { command: input.binary, args };
862
+ return { command: "sh", args: ["-c", 'echo $$ > "$1/cgroup.procs" && shift && exec "$@"', "sh", input.cgroup, input.binary, ...args] };
751
863
  }
752
864
  var STOP_WAIT_MS = 1e4;
753
865
  var POLL_MS = 20;
@@ -804,6 +916,7 @@ function createMicroVmPorts(over = {}) {
804
916
  assets: over.assets ?? ((runnerDir2) => ensureAssets({ runnerDir: runnerDir2 })),
805
917
  rootfs: over.rootfs ?? createRootfsHost(),
806
918
  firecracker: over.firecracker ?? createFirecrackerHost(),
919
+ cgroup: over.cgroup ?? realCgroupPort(),
807
920
  exec: over.exec ?? execCapture
808
921
  };
809
922
  }
@@ -1093,7 +1206,7 @@ function decode(prefix, gzip) {
1093
1206
  if (!gzip) return prefix;
1094
1207
  try {
1095
1208
  const { compressedWindow, inflatedBytes } = limits();
1096
- return gunzipSync(prefix.subarray(0, compressedWindow), { finishFlush: constants.Z_SYNC_FLUSH, maxOutputLength: inflatedBytes });
1209
+ return gunzipSync(prefix.subarray(0, compressedWindow), { finishFlush: constants$1.Z_SYNC_FLUSH, maxOutputLength: inflatedBytes });
1097
1210
  } catch (err) {
1098
1211
  const code = err.code;
1099
1212
  if (code === "ERR_BUFFER_TOO_LARGE") return refused(TOO_LONG);
@@ -1429,6 +1542,11 @@ function createGitRemote(options) {
1429
1542
  req.resume();
1430
1543
  return plain(res, 404, "not found\n");
1431
1544
  }
1545
+ if (options.readOnly === true && (route.kind === "receive-pack" || route.kind === "info-refs" && route.service === "git-receive-pack")) {
1546
+ log(`push refused: this remote is read-only`);
1547
+ req.resume();
1548
+ return plain(res, 403, "this remote is read-only: a check cannot push\n");
1549
+ }
1432
1550
  const own = isOwnRepository(options.repository, route.owner, route.name);
1433
1551
  if (own && route.kind === "receive-pack") return push(req, res, route);
1434
1552
  let kind = route.kind === "info-refs" && route.service === "git-receive-pack" ? "push" : "read";
@@ -1437,6 +1555,7 @@ function createGitRemote(options) {
1437
1555
  body = await readBody(req, maxLfsBodyBytes);
1438
1556
  if (body === void 0) return plain(res, 413, "the request is too large\n");
1439
1557
  kind = isLfsUpload(body) ? "push" : "read";
1558
+ if (options.readOnly === true && kind === "push") return plain(res, 403, "this remote is read-only: a check cannot upload\n");
1440
1559
  }
1441
1560
  let authorization;
1442
1561
  if (own) {
@@ -1913,7 +2032,8 @@ function gitRemoteFor(runtime, input) {
1913
2032
  onWorkflowRefused: () => {
1914
2033
  input.tokens.reason = WORKFLOW_PERMISSION_REASON;
1915
2034
  },
1916
- log: input.log
2035
+ log: input.log,
2036
+ ...input.readOnly === true ? { readOnly: true } : {}
1917
2037
  });
1918
2038
  }
1919
2039
  function firstLine4(text) {
@@ -1997,7 +2117,8 @@ async function provisionContainer(input) {
1997
2117
  repository,
1998
2118
  imageId: report.imageId,
1999
2119
  containerEnv: report.apply.containerEnv,
2000
- builtIns: true
2120
+ builtIns: true,
2121
+ lifecycle: report.apply.lifecycle
2001
2122
  };
2002
2123
  const env = buildEnvironment({
2003
2124
  config,
@@ -2063,7 +2184,9 @@ async function provisionContainer(input) {
2063
2184
  image: report.imageId,
2064
2185
  user: ADDED_USER,
2065
2186
  limits: limits2,
2066
- destinations: config.build?.destinations?.length ?? 0};
2187
+ destinations: config.build?.destinations?.length ?? 0,
2188
+ builtIns: true
2189
+ };
2067
2190
  const prepared = await host.run(prepareRunArgs(runInput), { logPath });
2068
2191
  if (prepared.status !== 0) return dockerFailed("preparing the build's volume", prepared);
2069
2192
  const started = await host.run(containerRunArgs(runInput), { logPath });
@@ -2149,6 +2272,40 @@ function containerCommandLine(input) {
2149
2272
  env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed }
2150
2273
  };
2151
2274
  }
2275
+ function containerSenderCommand(input) {
2276
+ const state = input.job.container;
2277
+ const env = buildEnvironment({
2278
+ config: input.config,
2279
+ item: input.job.item,
2280
+ itemKey: input.job.itemKey,
2281
+ branch: input.job.branch,
2282
+ repository: state.repository,
2283
+ hostEnv: input.runtime.hostEnv,
2284
+ containerEnv: state.containerEnv ?? {},
2285
+ builtIns: state.builtIns === true,
2286
+ identity: input.job.commitIdentity
2287
+ });
2288
+ const args = containerExecArgs({
2289
+ container: state.container,
2290
+ user: state.agentUser ?? ADDED_USER,
2291
+ workdir: CHECKOUT_DIR,
2292
+ env,
2293
+ argv: ["outerlayer", "session-sender"]
2294
+ });
2295
+ return { command: "docker", args: ["exec", ...args], env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed } };
2296
+ }
2297
+ function senderSignalScript(pidfile, signal) {
2298
+ const file = shellQuote(pidfile);
2299
+ return `p="$(cat ${file})" && [ -n "$p" ] && grep -qa session-sender "/proc/$p/cmdline" && kill -${signal} "$p"`;
2300
+ }
2301
+ async function signalInContainer(runtime, state, pidfile, signal) {
2302
+ await runtime.host.exec({
2303
+ container: state.container,
2304
+ user: state.agentUser ?? ADDED_USER,
2305
+ env: NO_ENV,
2306
+ argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)]
2307
+ });
2308
+ }
2152
2309
  async function stopContainer(runtime, state) {
2153
2310
  await runtime.host.stop(state.container);
2154
2311
  state.stopped = true;
@@ -2246,5 +2403,75 @@ async function cleanupContainer(runtime, state, jobDir) {
2246
2403
  rmSync(state.buildDir, { recursive: true, force: true });
2247
2404
  if (failures.length > 0) throw new Error(failures.join("; "));
2248
2405
  }
2406
+ async function startCheckContainer(input) {
2407
+ const { config, runtime, job, jobDir, tokens, log } = input;
2408
+ const { host } = runtime;
2409
+ const tag = `#${job.item}`;
2410
+ const built = job.container;
2411
+ const repository = built?.repository ?? job.repository;
2412
+ if (built === void 0 || built.imageId === void 0 || repository === void 0 || job.branch === void 0) {
2413
+ return { ok: false, reason: "the build's image, repository or branch is not recorded" };
2414
+ }
2415
+ const buildId = randomBytes(6).toString("hex");
2416
+ const name = `outerlayer-check-${buildId}`;
2417
+ const buildDir2 = buildDir(config.configPath, buildId);
2418
+ const agentUser = built.agentUser ?? ADDED_USER;
2419
+ const state = { buildId, container: name, volume: name, buildDir: buildDir2, repository, imageId: built.imageId, agentUser };
2420
+ const checksDir = join(jobDir, "checks");
2421
+ mkdirSync(checksDir, { recursive: true });
2422
+ const logPath = join(checksDir, "environment.log");
2423
+ input.save(state);
2424
+ const fail = async (reason) => {
2425
+ try {
2426
+ await cleanupContainer(runtime, state);
2427
+ } catch (err) {
2428
+ log.error(`${tag} cleanup of the check environment failed: ${describeCallFailure(err)}`);
2429
+ }
2430
+ return { ok: false, reason };
2431
+ };
2432
+ const dockerFailed = (what, result) => fail(`${what} failed (exit ${result.status})${firstLine4(result.stderr) === "" ? "" : `: ${firstLine4(result.stderr)}`}`);
2433
+ mkdirSync(buildDir2, { recursive: true });
2434
+ chmodSync(buildDir2, 493);
2435
+ try {
2436
+ const gitRemote = gitRemoteFor(runtime, { repository, branch: job.branch, tokens, buildDir: buildDir2, log: (line) => log.line(`${tag} ${line}`), readOnly: true });
2437
+ runtime.tunnels.set(buildId, await startServicesFor(runtime, config, buildDir2, checksDir, void 0, false, gitRemote));
2438
+ } catch (err) {
2439
+ return fail(`the tunnel could not start: ${describeCallFailure(err)}`);
2440
+ }
2441
+ const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096 };
2442
+ const runnerId = runnerLabelValue(config.configPath);
2443
+ const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId, [RUNNER_LABEL]: runnerId });
2444
+ if (volume.status !== 0) return dockerFailed("creating the check environment's volume", volume);
2445
+ const runInput = {
2446
+ buildId,
2447
+ runnerId,
2448
+ container: name,
2449
+ volume: name,
2450
+ buildDir: buildDir2,
2451
+ image: built.imageId,
2452
+ user: ADDED_USER,
2453
+ limits: limits2,
2454
+ destinations: config.build?.destinations?.length ?? 0,
2455
+ builtIns: false
2456
+ };
2457
+ const prepared = await host.run(prepareRunArgs(runInput), { logPath });
2458
+ if (prepared.status !== 0) return dockerFailed("preparing the check environment's volume", prepared);
2459
+ const started = await host.run(containerRunArgs(runInput), { logPath });
2460
+ if (started.status !== 0) return dockerFailed("starting the check environment's container", started);
2461
+ if (!await waitForRelay(runtime, state)) return fail("the relay in the check environment's container did not start");
2462
+ const env = checkEnvironment({ config, item: job.item, repository, hostEnv: runtime.hostEnv, containerEnv: built.containerEnv ?? {} });
2463
+ return {
2464
+ ok: true,
2465
+ container: {
2466
+ state,
2467
+ exec: (argv, workdir, opts) => host.exec({ container: name, user: agentUser, workdir, env, argv }, opts?.logPath === void 0 ? void 0 : { logPath: opts.logPath }),
2468
+ restart: async () => {
2469
+ await host.stop(name);
2470
+ const again = await host.start(name);
2471
+ return again.status === 0 && await waitForRelay(runtime, state);
2472
+ }
2473
+ }
2474
+ };
2475
+ }
2249
2476
 
2250
- export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, shellQuote, shortClaim, stageGovernedContext, startServicesFor, stopContainer, syncInContainer };
2477
+ export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, containerSenderCommand, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, outOfMemoryReason, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, senderSignalScript, setUpRunnerCgroup, shellQuote, shortClaim, signalInContainer, stageGovernedContext, startCheckContainer, startServicesFor, stopContainer, syncInContainer };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-WO2BXCTQ.js';
4
- import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-EMY4I27X.js';
3
+ import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-VXLJORHL.js';
4
+ import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-7ZR5UPMB.js';
5
5
  import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, readdirSync } from 'fs';
6
6
  import { join, dirname, relative, sep } from 'path';
7
7
 
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+
4
+ createRequire(import.meta.url);
5
+
6
+ // src/runner/vm/service.ts
7
+ var VM_SERVICE_NAME = "outerlayer-runner";
8
+ var UNIT_MARKER = "# Written by outerlayer runner install --service; edit the runner config, not this file.";
9
+ var STOP_MARGIN_MINUTES = 10;
10
+ function escapeUnitPath(path) {
11
+ return path.replace(/%/g, "%%").replace(/ /g, "\\x20");
12
+ }
13
+ function runnerUnit(unit) {
14
+ return `${UNIT_MARKER}
15
+ [Unit]
16
+ Description=OuterLayer runner
17
+ After=network-online.target${unit.docker ? " docker.service" : ""}
18
+ Wants=network-online.target
19
+ ${unit.docker ? "Requires=docker.service\n" : ""}
20
+ [Service]
21
+ User=${unit.user}
22
+ ExecStart=${unit.execStart}
23
+ EnvironmentFile=-${escapeUnitPath(`${unit.configDir}/runner.env`)}
24
+ Restart=on-failure
25
+ RestartForceExitStatus=75
26
+ RestartSec=30
27
+ KillSignal=SIGTERM
28
+ KillMode=mixed
29
+ TimeoutStopSec=${unit.timeLimitMinutes + STOP_MARGIN_MINUTES}min
30
+ OOMPolicy=continue
31
+ Delegate=yes
32
+
33
+ [Install]
34
+ WantedBy=multi-user.target
35
+ `;
36
+ }
37
+
38
+ export { UNIT_MARKER, VM_SERVICE_NAME, runnerUnit };
@@ -27,7 +27,12 @@ function launchesPath(home) {
27
27
  }
28
28
  function appendLaunchRecord(input, home) {
29
29
  mkdirSync(join(home, ".outerlayer", "spool"), { recursive: true });
30
- const record = { sessionId: input.sessionId, item: input.item, t: input.t ?? (/* @__PURE__ */ new Date()).toISOString() };
30
+ const record = {
31
+ sessionId: input.sessionId,
32
+ item: input.item,
33
+ ...input.job === void 0 ? {} : { job: input.job },
34
+ t: input.t ?? (/* @__PURE__ */ new Date()).toISOString()
35
+ };
31
36
  appendFileSync(launchesPath(home), `${JSON.stringify(record)}
32
37
  `);
33
38
  }
@@ -75,22 +80,28 @@ function launchedItemsBySession(home, readImpl = readLaunchSpool) {
75
80
  }
76
81
  function readLaunchSpoolState(home, readImpl = readLaunchSpool) {
77
82
  const ids = /* @__PURE__ */ new Set();
83
+ const records = /* @__PURE__ */ new Map();
78
84
  const read = readImpl(launchesPath(home));
79
- if (read.kind === "unreadable") return { ids, unreadable: read.reason };
80
- if (read.kind === "absent") return { ids };
85
+ if (read.kind === "unreadable") return { ids, records, unreadable: read.reason };
86
+ if (read.kind === "absent") return { ids, records };
81
87
  let malformedLines = 0;
82
88
  for (const line of read.text.split("\n")) {
83
89
  const text = line.trim();
84
90
  if (!text) continue;
85
91
  try {
86
92
  const record = JSON.parse(text);
87
- if (typeof record.sessionId === "string" && record.sessionId !== "") ids.add(record.sessionId);
88
- else malformedLines += 1;
93
+ if (typeof record.sessionId === "string" && record.sessionId !== "") {
94
+ ids.add(record.sessionId);
95
+ records.set(record.sessionId, {
96
+ ...typeof record.item === "number" ? { item: record.item } : {},
97
+ ...typeof record.job === "string" ? { job: record.job } : {}
98
+ });
99
+ } else malformedLines += 1;
89
100
  } catch {
90
101
  malformedLines += 1;
91
102
  }
92
103
  }
93
- return { ids, ...malformedLines > 0 ? { malformedLines } : {} };
104
+ return { ids, records, ...malformedLines > 0 ? { malformedLines } : {} };
94
105
  }
95
106
 
96
107
  export { appendLaunchRecord, launchStatusIsDecisive, launchedItemForSession, launchedItemsBySession, launchesPath, parseWorkItemEnv, readLaunchSpoolState };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { readLaunchSpoolState, launchesPath } from './chunk-DVDEBNQJ.js';
3
+ import { readLaunchSpoolState, launchesPath } from './chunk-GQD22KKN.js';
4
4
 
5
5
  createRequire(import.meta.url);
6
6
 
@@ -17,18 +17,33 @@ function launchRefusalForSession(sessionId, home) {
17
17
  return `this session was not launched with OUTERLAYER_WORK naming a work item, so nothing it produces may leave the machine. Start the session with OUTERLAYER_WORK set (for example OUTERLAYER_WORK=42), or run this from CI.`;
18
18
  }
19
19
  var LaunchFilter = class {
20
- constructor(launchedSessionIds) {
20
+ /**
21
+ * `records`, when given, scopes a live sender:
22
+ * - a build sender (`build`) admits only a session launched for its item in its own job; a record with no job
23
+ * is one an older hook wrote, which is admitted on the item alone;
24
+ * - any other live sender skips a session launched inside a runner job, which only that job's sender sends.
25
+ * `sync` passes no records and decides by the launch record's existence alone.
26
+ */
27
+ constructor(launchedSessionIds, records, build) {
21
28
  this.launchedSessionIds = launchedSessionIds;
29
+ this.records = records;
30
+ this.build = build;
22
31
  /** Sessions dropped for carrying no launch record — for the sync summary. */
23
32
  this.skippedTotal = 0;
24
33
  }
25
34
  /** True when the session should sync. Records skips for reporting. */
26
35
  allows(session) {
27
36
  const key = session.subagent?.parentSessionId ?? session.id;
28
- const allowed = typeof key === "string" && this.launchedSessionIds.has(key);
37
+ const allowed = typeof key === "string" && this.launchedSessionIds.has(key) && this.inScope(this.records?.get(key));
29
38
  if (!allowed) this.skippedTotal += 1;
30
39
  return allowed;
31
40
  }
41
+ inScope(facts) {
42
+ if (this.build !== void 0) {
43
+ return facts !== void 0 && facts.item === this.build.item && (facts.job === void 0 || facts.job === this.build.jobDir);
44
+ }
45
+ return facts?.job === void 0;
46
+ }
32
47
  };
33
48
 
34
49
  export { LaunchFilter, launchRefusalForSession };