@outerlayer/cli 0.5.0 → 0.6.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 (70) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +9 -0
  3. package/dist/{agent-setup-IFH3FB5X.js → agent-setup-USZ2SAXC.js} +2 -2
  4. package/dist/build-info.json +1 -1
  5. package/dist/{check-AXCXDYO2.js → check-CCVPBN7A.js} +26 -14
  6. package/dist/{chunk-5Y3QQRUH.js → chunk-3DD6AOWC.js} +37 -0
  7. package/dist/{chunk-WOBNRVQT.js → chunk-3DEVYTNQ.js} +1 -1
  8. package/dist/{chunk-L24G4PLP.js → chunk-43J3M6KB.js} +486 -21
  9. package/dist/{chunk-FC7TMRNA.js → chunk-4NLKY4WR.js} +2 -2
  10. package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
  11. package/dist/{chunk-D2KWEAN6.js → chunk-7DBI6SNT.js} +1 -1
  12. package/dist/{chunk-QDQEUVUF.js → chunk-AIW6GLET.js} +1 -1
  13. package/dist/{chunk-EX6M5JPR.js → chunk-ANK2B36Y.js} +131 -15
  14. package/dist/{chunk-RMAZ2NWA.js → chunk-BNMCXPG7.js} +14 -2
  15. package/dist/{chunk-UFFXNXLQ.js → chunk-FA2BOX4O.js} +31 -29
  16. package/dist/chunk-FBTE2A2X.js +38 -0
  17. package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
  18. package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
  19. package/dist/{chunk-JLDZTG3A.js → chunk-IASWYRVG.js} +1 -1
  20. package/dist/{install-cmd-7WXOIFDO.js → chunk-JWOQNNN2.js} +0 -1
  21. package/dist/{chunk-ZLB7GCTY.js → chunk-MIGEDH5P.js} +19 -11
  22. package/dist/{chunk-OHFDWLJP.js → chunk-MXB5VB4Y.js} +18 -6
  23. package/dist/chunk-NQXFAGHU.js +211 -0
  24. package/dist/chunk-OUUINEYB.js +27 -0
  25. package/dist/{chunk-OR6AEEDO.js → chunk-PMAMOSTP.js} +3 -3
  26. package/dist/{chunk-RJ467PDQ.js → chunk-RBOMWNOG.js} +1 -1
  27. package/dist/{chunk-4NMBDRBS.js → chunk-RZSF5B46.js} +289 -130
  28. package/dist/chunk-T6OIJQRT.js +31 -0
  29. package/dist/chunk-UGP76IZ5.js +34 -0
  30. package/dist/{chunk-EGABNHZG.js → chunk-UMFWVGJO.js} +148 -18
  31. package/dist/{chunk-RWOTHXYE.js → chunk-VMJ3MMBX.js} +206 -71
  32. package/dist/chunk-YYYRBYWG.js +32 -0
  33. package/dist/chunk-ZILGTTY6.js +75 -0
  34. package/dist/{cli-NNWPFP5S.js → cli-7WCNPBYC.js} +147 -69
  35. package/dist/command-ARFK3OYF.js +162 -0
  36. package/dist/{config-XVJYZ4IQ.js → config-HL2AIR3K.js} +1 -1
  37. package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-PCESZLTR.js} +4 -4
  38. package/dist/{context-adopt-UMA4O5NY.js → context-adopt-G5JAELE7.js} +2 -2
  39. package/dist/{context-materialize-G2FUFC72.js → context-materialize-3HLPZAF6.js} +2 -2
  40. package/dist/{dist-WDV7U7UJ.js → dist-RDLP4GGJ.js} +1 -1
  41. package/dist/{doctor-J3GIMR7K.js → doctor-VOMYOHQU.js} +47 -13
  42. package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-TBF46VXZ.js} +6 -7
  43. package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-3427XKFW.js} +3 -3
  44. package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-GSI75K36.js} +3 -3
  45. package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-3PRQKB55.js} +6 -7
  46. package/dist/{hook-fast-EYENPK6F.js → hook-fast-NJBBZQ5Q.js} +1 -1
  47. package/dist/index.js +3 -2
  48. package/dist/{init-CZX7ZDWF.js → init-OHYFIKZD.js} +27 -56
  49. package/dist/{init-3ZQLFPNC.js → init-WVR4BZQ4.js} +8 -8
  50. package/dist/{init-cmd-W5NOLQPX.js → init-cmd-HMFNIFPI.js} +7 -7
  51. package/dist/inspect-XAUSB7L3.js +63 -0
  52. package/dist/install-DSUZE2TJ.js +13 -0
  53. package/dist/install-cmd-453C7IZT.js +8 -0
  54. package/dist/lima-7NPCNGWT.js +6 -0
  55. package/dist/{loop-VGM772I7.js → loop-UAJB34KA.js} +26 -11
  56. package/dist/{machine-5WEJA36F.js → machine-2JOWGRSZ.js} +7 -7
  57. package/dist/record-C43U4PKS.js +7 -0
  58. package/dist/session-sender-ED26V45B.js +28 -0
  59. package/dist/{status-KBDVUYQ3.js → status-6WNXX2O7.js} +42 -10
  60. package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-YONVGUYS.js} +6 -7
  61. package/dist/{watch-3YW6Z3DQ.js → watch-HHKAF26L.js} +6 -7
  62. package/dist/{work-claim-cmd-BQPPURBW.js → work-claim-cmd-LXBZTMJA.js} +6 -6
  63. package/dist/{work-cmd-NUR4CAG6.js → work-cmd-OVNF44FY.js} +3 -3
  64. package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-6DGQNTAH.js} +3 -3
  65. package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
  66. package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-FCZHFHZS.js} +4 -4
  67. package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-QJJVHZDC.js} +2 -2
  68. package/package.json +2 -1
  69. package/dist/chunk-ZOC7JSYC.js +0 -83
  70. package/dist/{chunk-XDDW4FRS.js → chunk-OIRNH6UO.js} +1 -1
@@ -44368,14 +44368,14 @@ var ModelRegistryImpl = class {
44368
44368
  };
44369
44369
  var models_default = {
44370
44370
  version: "1.0.0",
44371
- generatedAt: "2026-10-03T04:32:34.999Z",
44371
+ generatedAt: "2026-10-04T05:04:28.506Z",
44372
44372
  sources: {
44373
44373
  litellm: {
44374
- fetchedAt: "2026-10-03T04:32:34.999Z",
44375
- modelCount: 1882
44374
+ fetchedAt: "2026-10-04T05:04:28.506Z",
44375
+ modelCount: 1887
44376
44376
  },
44377
44377
  openrouter: {
44378
- fetchedAt: "2026-10-03T04:32:34.999Z",
44378
+ fetchedAt: "2026-10-04T05:04:28.506Z",
44379
44379
  modelCount: 198
44380
44380
  }
44381
44381
  },
@@ -65888,12 +65888,12 @@ var models_default = {
65888
65888
  "top_p"
65889
65889
  ],
65890
65890
  pricing: {
65891
- inputCostPerToken: 29e-8,
65892
- outputCostPerToken: 114e-8
65891
+ inputCostPerToken: 25e-8,
65892
+ outputCostPerToken: 1e-6
65893
65893
  },
65894
65894
  context: {
65895
65895
  maxInputTokens: 163840,
65896
- maxOutputTokens: 115200
65896
+ maxOutputTokens: 147456
65897
65897
  },
65898
65898
  capabilities: {
65899
65899
  functionCalling: true,
@@ -70352,7 +70352,10 @@ var models_default = {
70352
70352
  provider: "azure",
70353
70353
  displayName: "Flux.2 Pro",
70354
70354
  mode: "image_generation",
70355
- source: "litellm"
70355
+ source: "litellm",
70356
+ context: {
70357
+ maxInputTokens: 32e3
70358
+ }
70356
70359
  },
70357
70360
  "Llama-3.2-11B-Vision-Instruct": {
70358
70361
  provider: "azure",
@@ -73327,6 +73330,9 @@ var models_default = {
73327
73330
  displayName: "MAI Image 2.5",
73328
73331
  mode: "image_generation",
73329
73332
  source: "litellm",
73333
+ context: {
73334
+ maxInputTokens: 32e3
73335
+ },
73330
73336
  deprecationDate: "2026-10-01"
73331
73337
  },
73332
73338
  "MAI-Image-2.5-Flash": {
@@ -73334,6 +73340,9 @@ var models_default = {
73334
73340
  displayName: "MAI Image 2.5 Flash",
73335
73341
  mode: "image_generation",
73336
73342
  source: "litellm",
73343
+ context: {
73344
+ maxInputTokens: 32e3
73345
+ },
73337
73346
  deprecationDate: "2026-10-01"
73338
73347
  },
73339
73348
  "MAI-Image-2e": {
@@ -79250,6 +79259,10 @@ var models_default = {
79250
79259
  inputCostPerToken: 2e-6,
79251
79260
  outputCostPerToken: 12e-6
79252
79261
  },
79262
+ context: {
79263
+ maxInputTokens: 98304,
79264
+ maxOutputTokens: 32768
79265
+ },
79253
79266
  capabilities: {
79254
79267
  functionCalling: false,
79255
79268
  audioInput: true
@@ -79264,6 +79277,10 @@ var models_default = {
79264
79277
  inputCostPerToken: 35e-7,
79265
79278
  outputCostPerToken: 21e-6
79266
79279
  },
79280
+ context: {
79281
+ maxInputTokens: 131072,
79282
+ maxOutputTokens: 65536
79283
+ },
79267
79284
  capabilities: {
79268
79285
  functionCalling: false,
79269
79286
  audioInput: true
@@ -84381,6 +84398,9 @@ var models_default = {
84381
84398
  displayName: "MAI Image 2.5 Pro",
84382
84399
  mode: "image_generation",
84383
84400
  source: "litellm",
84401
+ context: {
84402
+ maxInputTokens: 32e3
84403
+ },
84384
84404
  deprecationDate: "2026-10-01"
84385
84405
  },
84386
84406
  "ap-northeast-1/qwen.qwen3-next-80b-a3b": {
@@ -87196,13 +87216,19 @@ var models_default = {
87196
87216
  provider: "azure",
87197
87217
  displayName: "MAI Image 2.6",
87198
87218
  mode: "image_generation",
87199
- source: "litellm"
87219
+ source: "litellm",
87220
+ context: {
87221
+ maxInputTokens: 32e3
87222
+ }
87200
87223
  },
87201
87224
  "MAI-Image-2.6-Flash": {
87202
87225
  provider: "azure",
87203
87226
  displayName: "MAI Image 2.6 Flash",
87204
87227
  mode: "image_generation",
87205
- source: "litellm"
87228
+ source: "litellm",
87229
+ context: {
87230
+ maxInputTokens: 32e3
87231
+ }
87206
87232
  },
87207
87233
  "FW-DeepSeek-V4.1-Flash": {
87208
87234
  provider: "azure",
@@ -88198,6 +88224,72 @@ var models_default = {
88198
88224
  }
88199
88225
  ]
88200
88226
  }
88227
+ },
88228
+ "embed-v5.0-pro": {
88229
+ provider: "cohere",
88230
+ displayName: "Embed V5.0 Pro",
88231
+ mode: "embedding",
88232
+ source: "litellm",
88233
+ pricing: {
88234
+ inputCostPerToken: 12e-8,
88235
+ outputCostPerToken: 0
88236
+ },
88237
+ context: {
88238
+ maxInputTokens: 128e3
88239
+ }
88240
+ },
88241
+ "embed-v5.0-fast": {
88242
+ provider: "cohere",
88243
+ displayName: "Embed V5.0 Fast",
88244
+ mode: "embedding",
88245
+ source: "litellm",
88246
+ pricing: {
88247
+ inputCostPerToken: 8e-8,
88248
+ outputCostPerToken: 0
88249
+ },
88250
+ context: {
88251
+ maxInputTokens: 128e3
88252
+ }
88253
+ },
88254
+ "pplx-decider-v1-27b": {
88255
+ provider: "perplexity",
88256
+ displayName: "Pplx Decider V1 27b",
88257
+ mode: "chat",
88258
+ source: "litellm",
88259
+ pricing: {
88260
+ inputCostPerToken: 4e-8,
88261
+ outputCostPerToken: 0
88262
+ },
88263
+ context: {
88264
+ maxInputTokens: 262144
88265
+ }
88266
+ },
88267
+ "grok-imagine-video-1.5-lite": {
88268
+ provider: "xai",
88269
+ displayName: "Grok Imagine Video 1.5 Lite",
88270
+ mode: "chat",
88271
+ source: "litellm"
88272
+ },
88273
+ "kimi-k2-thinking": {
88274
+ provider: "azure",
88275
+ displayName: "Kimi K2 Thinking",
88276
+ mode: "chat",
88277
+ source: "litellm",
88278
+ pricing: {
88279
+ inputCostPerToken: 6e-7,
88280
+ outputCostPerToken: 25e-7
88281
+ },
88282
+ context: {
88283
+ maxInputTokens: 262144,
88284
+ maxOutputTokens: 262144
88285
+ },
88286
+ capabilities: {
88287
+ vision: false,
88288
+ functionCalling: true,
88289
+ promptCaching: false,
88290
+ reasoning: true
88291
+ },
88292
+ deprecationDate: "2026-03-29"
88201
88293
  }
88202
88294
  }
88203
88295
  };
@@ -90223,7 +90315,8 @@ function scanAll(opts = {}) {
90223
90315
  var CaptureDaemon = class {
90224
90316
  constructor(options = {}) {
90225
90317
  this.pending = /* @__PURE__ */ new Map();
90226
- var _a5, _b, _c, _d, _e, _f, _g;
90318
+ this.reported = /* @__PURE__ */ new Map();
90319
+ var _a5, _b, _c, _d, _e, _f, _g, _h;
90227
90320
  this.root = (_a5 = options.root) != null ? _a5 : defaultProjectsDir();
90228
90321
  this.rawRoot = (_b = options.rawRoot) != null ? _b : defaultRawDir();
90229
90322
  this.debounceMs = (_c = options.debounceMs) != null ? _c : 2e3;
@@ -90233,18 +90326,23 @@ var CaptureDaemon = class {
90233
90326
  this.excludeProjects = options.excludeProjects;
90234
90327
  this.log = (_f = options.onLog) != null ? _f : (() => void 0);
90235
90328
  this.now = (_g = options.now) != null ? _g : Date.now;
90329
+ this.watchOnly = (_h = options.watchOnly) != null ? _h : false;
90236
90330
  }
90237
90331
  /** Begin watching. Resolves once the initial scan completes. */
90238
90332
  async start() {
90239
- mkdirSync(this.rawRoot, { recursive: true });
90333
+ if (!this.watchOnly) mkdirSync(this.rawRoot, { recursive: true });
90240
90334
  this.rescanNow();
90241
- this.watcher = watch(`${this.root}/**/*.jsonl`, {
90335
+ if (this.watchOnly) mkdirSync(this.root, { recursive: true });
90336
+ this.watcher = watch(this.watchOnly ? this.root : `${this.root}/**/*.jsonl`, {
90242
90337
  ignoreInitial: true,
90338
+ ...this.watchOnly ? { ignored: (path, stats) => (stats == null ? void 0 : stats.isFile()) === true && !path.endsWith(".jsonl") } : {},
90243
90339
  awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
90244
90340
  });
90341
+ const ready = this.watchOnly ? new Promise((resolve3) => this.watcher.once("ready", () => resolve3())) : void 0;
90245
90342
  this.watcher.on("add", (f) => this.schedule(f));
90246
90343
  this.watcher.on("change", (f) => this.schedule(f));
90247
90344
  this.rescanTimer = setInterval(() => this.rescanNow(), this.rescanMs);
90345
+ await ready;
90248
90346
  this.log({ type: "started" });
90249
90347
  }
90250
90348
  async stop() {
@@ -90263,7 +90361,8 @@ var CaptureDaemon = class {
90263
90361
  file22,
90264
90362
  setTimeout(() => {
90265
90363
  this.pending.delete(file22);
90266
- this.mirror(file22);
90364
+ if (this.watchOnly) this.report(file22);
90365
+ else this.mirror(file22);
90267
90366
  }, this.debounceMs)
90268
90367
  );
90269
90368
  }
@@ -90275,6 +90374,21 @@ var CaptureDaemon = class {
90275
90374
  if (this.excludeProjects && this.excludeProjects.test(projectDir)) return false;
90276
90375
  return true;
90277
90376
  }
90377
+ /** Watch-only mode: names a source transcript that exists and is not empty. Nothing is copied or evicted. */
90378
+ report(file22) {
90379
+ if (!this.included(file22)) return;
90380
+ let signature;
90381
+ try {
90382
+ const st = statSync(file22);
90383
+ if (st.size === 0) return;
90384
+ signature = `${st.size}:${st.mtimeMs}`;
90385
+ } catch {
90386
+ return;
90387
+ }
90388
+ if (this.reported.get(file22) === signature) return;
90389
+ this.reported.set(file22, signature);
90390
+ this.log({ type: "changed", file: file22 });
90391
+ }
90278
90392
  /** Copy one transcript to the raw mirror atomically. Public for the tailer
90279
90393
  * loop and tests. Returns bytes written, or null if skipped/unchanged. */
90280
90394
  mirror(file22) {
@@ -90308,7 +90422,9 @@ var CaptureDaemon = class {
90308
90422
  rescanNow() {
90309
90423
  const files = this.listJsonl(this.root);
90310
90424
  for (const file22 of files) {
90311
- if (this.included(file22)) this.mirror(file22);
90425
+ if (!this.included(file22)) continue;
90426
+ if (this.watchOnly) this.report(file22);
90427
+ else this.mirror(file22);
90312
90428
  }
90313
90429
  this.log({ type: "rescan", detail: `${files.length} files` });
90314
90430
  }
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { detectActiveSession } from './chunk-BFESLKPP.js';
4
- import { sanitizeTerminal } from './chunk-WOBNRVQT.js';
5
- import { resolveRepoIdentity } from './chunk-EX6M5JPR.js';
4
+ import { sanitizeTerminal } from './chunk-3DEVYTNQ.js';
5
+ import { resolveRepoIdentity } from './chunk-ANK2B36Y.js';
6
6
  import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
7
7
  import { defaultFetch } from './chunk-BCLJSQCV.js';
8
8
  import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
@@ -337,6 +337,18 @@ async function runWorkBuild(opts) {
337
337
  return { data, output };
338
338
  }
339
339
  async function runWorkLinkSession(opts) {
340
+ try {
341
+ return await linkSession(opts);
342
+ } catch (err) {
343
+ if (opts.logFailures) {
344
+ const { logHookError } = await import('./hook-fast-NJBBZQ5Q.js');
345
+ const reason = err instanceof Error ? err.message : String(err);
346
+ logHookError(opts.home ?? homeFromEnv(opts.env ?? process.env), new Error(`work link-session #${opts.number} failed: ${reason}`));
347
+ }
348
+ throw err;
349
+ }
350
+ }
351
+ async function linkSession(opts) {
340
352
  const cwd = opts.cwd ?? process.cwd();
341
353
  const env = opts.env ?? process.env;
342
354
  const home = opts.home ?? homeFromEnv(env);
@@ -3,7 +3,7 @@ import { createRequire } from 'node:module';
3
3
  import { deadHooksState, huskyPrepareScript, preparePackageManagerCommand } from './chunk-XCCLFVXM.js';
4
4
  import { readRepoConnection, findGitDirs } from './chunk-BJ3KTMDH.js';
5
5
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
6
- import { readFileSync, mkdirSync, writeFileSync, renameSync, existsSync, statSync, appendFileSync, openSync, opendirSync, unlinkSync } from 'fs';
6
+ import { readFileSync, mkdirSync, writeFileSync, renameSync, existsSync, statSync, appendFileSync, opendirSync, unlinkSync } from 'fs';
7
7
  import { spawn, execFileSync } from 'child_process';
8
8
  import { createHash } from 'crypto';
9
9
  import { join, dirname, resolve } from 'path';
@@ -347,18 +347,11 @@ var readStdin = () => {
347
347
  }
348
348
  };
349
349
  var AUTO_SYNC_DEBOUNCE_MS = 5 * 60 * 1e3;
350
- function appendFd(path) {
351
- if (path === void 0) return "ignore";
352
- try {
353
- return openSync(path, "a");
354
- } catch {
355
- return "ignore";
356
- }
357
- }
358
- var spawnDetached = (command, args, stderrPath, cwd) => {
350
+ var LOG_FAILURES_FLAG = "--log-failures";
351
+ var spawnDetached = (command, args, cwd) => {
359
352
  const child = spawn(command, args, {
360
353
  detached: true,
361
- stdio: ["ignore", "ignore", appendFd(stderrPath)],
354
+ stdio: ["ignore", "ignore", "ignore"],
362
355
  ...cwd !== void 0 ? { cwd } : {}
363
356
  });
364
357
  child.on("error", () => {
@@ -382,7 +375,7 @@ function maybeAutoSync(event, home = homeFromEnv(), spawnImpl = spawnDetached, n
382
375
  if (!config.appId) return;
383
376
  } else if (!config.accountToken || readRepoConnection(cwd) === void 0) return;
384
377
  writeFileSync(marker, String(now));
385
- spawnImpl(process.execPath, [process.argv[1], "sync", "--quiet"], void 0, cwd);
378
+ spawnImpl(process.execPath, [process.argv[1], "sync", "--quiet"], cwd);
386
379
  } catch {
387
380
  }
388
381
  }
@@ -395,13 +388,13 @@ function hookErrorsContext(home, writtenBytes, seenBytes) {
395
388
  }
396
389
  async function handleWorkLaunch(sessionId, home, env, spawnImpl = spawnDetached, cwd = process.cwd()) {
397
390
  if (!sessionId || env.OUTERLAYER_WORK === void 0) return void 0;
398
- const { parseWorkItemEnv, appendLaunchRecord } = await import('./work-launch-YI4CJDAP.js');
391
+ const { parseWorkItemEnv, appendLaunchRecord } = await import('./work-launch-R5HTAP6X.js');
399
392
  const parsed = parseWorkItemEnv(env.OUTERLAYER_WORK);
400
393
  if ("reason" in parsed) {
401
394
  logHookError(home, new Error(`OUTERLAYER_WORK ignored: ${parsed.reason}`));
402
395
  return `OuterLayer ignored this session's OUTERLAYER_WORK value: ${sanitizeForContext(parsed.reason)}. The session is not attached to a work item and will not upload. Hook failures are logged to ${join(spoolDir(home), "hook-errors.log")}.`;
403
396
  }
404
- appendLaunchRecord({ sessionId, item: parsed.number }, home);
397
+ appendLaunchRecord({ sessionId, item: parsed.number, ...env.OUTERLAYER_JOB_DIR === void 0 ? {} : { job: env.OUTERLAYER_JOB_DIR } }, home);
405
398
  const args = [
406
399
  process.argv[1],
407
400
  "work",
@@ -417,17 +410,16 @@ async function handleWorkLaunch(sessionId, home, env, spawnImpl = spawnDetached,
417
410
  // hook has already returned) and cannot double-link, because the
418
411
  // link is keyed by the session id passed above.
419
412
  "--retry",
420
- String(WORK_LINK_RETRIES)
413
+ String(WORK_LINK_RETRIES),
414
+ LOG_FAILURES_FLAG
421
415
  ];
422
416
  if (env.OUTERLAYER_JOB_DIR !== void 0) args.push("--no-lease");
423
417
  spawnImpl(
424
418
  process.execPath,
425
419
  args,
426
- // The link runs after this hook has exited, so its own failure
427
- // (offline, revoked key, unknown item number) has nowhere else to be
428
- // seen: without this the session uploads later with no Work link and
429
- // no trace of why.
430
- join(spoolDir(home), "hook-errors.log"),
420
+ // The link appends its own failure line to hook-errors.log (see
421
+ // `--log-failures`), so a Node warning on a link that worked never
422
+ // reaches the log.
431
423
  cwd
432
424
  );
433
425
  return void 0;
@@ -456,30 +448,28 @@ async function maybeRenewWorkLease(event, sessionId, home = homeFromEnv(), env =
456
448
  if (now - statSync(marker).mtimeMs < WORK_LEASE_RENEW_DEBOUNCE_MS) return;
457
449
  } catch {
458
450
  }
459
- const { parseWorkItemEnv } = await import('./work-launch-YI4CJDAP.js');
451
+ const { parseWorkItemEnv } = await import('./work-launch-R5HTAP6X.js');
460
452
  const parsed = parseWorkItemEnv(env.OUTERLAYER_WORK);
461
453
  if ("reason" in parsed) return;
462
454
  writeFileSync(marker, String(now));
463
455
  spawnImpl(
464
456
  process.execPath,
465
- [process.argv[1], "work", "link-session", String(parsed.number), "--session-id", sessionId],
466
- join(spoolDir(home), "hook-errors.log"),
457
+ [process.argv[1], "work", "link-session", String(parsed.number), "--session-id", sessionId, LOG_FAILURES_FLAG],
467
458
  cwd
468
459
  );
469
460
  } catch {
470
461
  }
471
462
  }
472
- async function handleWorkSessionEnd(event, sessionId, home = homeFromEnv(), env = process.env, spawnImpl = spawnDetached, cwd = process.cwd()) {
463
+ async function handleWorkSessionEnd(event, sessionId, _home = homeFromEnv(), env = process.env, spawnImpl = spawnDetached, cwd = process.cwd()) {
473
464
  try {
474
465
  if (event !== "SessionEnd") return;
475
466
  if (!sessionId || env.OUTERLAYER_WORK === void 0) return;
476
- const { parseWorkItemEnv } = await import('./work-launch-YI4CJDAP.js');
467
+ const { parseWorkItemEnv } = await import('./work-launch-R5HTAP6X.js');
477
468
  const parsed = parseWorkItemEnv(env.OUTERLAYER_WORK);
478
469
  if ("reason" in parsed) return;
479
470
  spawnImpl(
480
471
  process.execPath,
481
- [process.argv[1], "work", "link-session", String(parsed.number), "--session-id", sessionId, "--ended"],
482
- join(spoolDir(home), "hook-errors.log"),
472
+ [process.argv[1], "work", "link-session", String(parsed.number), "--session-id", sessionId, "--ended", LOG_FAILURES_FLAG],
483
473
  cwd
484
474
  );
485
475
  } catch {
@@ -638,7 +628,7 @@ async function runHookFast(event, home = homeFromEnv(), read = readStdin, env =
638
628
  recordHookErrorsSeen(home, hookErrorsBytes);
639
629
  if (contextPossiblyGoverned(sessionCwd, home)) {
640
630
  try {
641
- const { materializeContext: materialize } = await import('./context-materialize-G2FUFC72.js');
631
+ const { materializeContext: materialize } = await import('./context-materialize-3HLPZAF6.js');
642
632
  const sessionId = typeof payload.session_id === "string" ? payload.session_id : "unknown";
643
633
  const result = await materialize({ cwd: sessionCwd, sessionId, home });
644
634
  materializeMessage = result.message;
@@ -677,7 +667,9 @@ async function runHookFast(event, home = homeFromEnv(), read = readStdin, env =
677
667
  logHookError(home, err);
678
668
  }
679
669
  }
670
+ var hookErrorsLogged = 0;
680
671
  function logHookError(home, err) {
672
+ hookErrorsLogged++;
681
673
  try {
682
674
  const dir = spoolDir(home);
683
675
  mkdirSync(dir, { recursive: true });
@@ -690,5 +682,15 @@ function logHookError(home, err) {
690
682
  } catch {
691
683
  }
692
684
  }
685
+ function recordLinkExitFailure(argv, home = homeFromEnv(), target = process) {
686
+ const at = argv.indexOf("link-session");
687
+ if (at < 0 || !argv.includes(LOG_FAILURES_FLAG)) return;
688
+ const number = argv[at + 1] ?? "?";
689
+ target.once("exit", (code) => {
690
+ if (code !== 0 && hookErrorsLogged === 0) {
691
+ logHookError(home, new Error(`work link-session #${number} exited with code ${code} before it reported a failure`));
692
+ }
693
+ });
694
+ }
693
695
 
694
- export { AUTO_SYNC_DEBOUNCE_MS, SYNC_STALE_AFTER_MS, WORK_LEASE_RENEW_DEBOUNCE_MS, WORK_LINK_RETRIES, assessSyncHealth, checkInstalledCliStaleness, contextPossiblyGoverned, formatAgo, handleWorkLaunch, handleWorkSessionEnd, hookFiredMarkerPath, installedCliDistDir, maybeAutoSync, maybeRenewWorkLease, readSyncStatus, refreshCommand, runHookFast, sanitizeForContext, spoolDir, workspaceRootOf, writeSyncStatus };
696
+ export { AUTO_SYNC_DEBOUNCE_MS, SYNC_STALE_AFTER_MS, WORK_LEASE_RENEW_DEBOUNCE_MS, WORK_LINK_RETRIES, assessSyncHealth, checkInstalledCliStaleness, contextPossiblyGoverned, formatAgo, handleWorkLaunch, handleWorkSessionEnd, hookFiredMarkerPath, installedCliDistDir, logHookError, maybeAutoSync, maybeRenewWorkLease, readSyncStatus, recordLinkExitFailure, refreshCommand, runHookFast, sanitizeForContext, spoolDir, workspaceRootOf, writeSyncStatus };
@@ -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 };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { detectActiveSession } from './chunk-BFESLKPP.js';
4
- import { resolveRepoIdentity } from './chunk-EX6M5JPR.js';
4
+ import { resolveRepoIdentity } from './chunk-ANK2B36Y.js';
5
5
  import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
6
6
  import { defaultFetch } from './chunk-BCLJSQCV.js';
7
7
  import { unauthorizedMessage, resolveGatewayCredentials } from './chunk-BOLTI6LR.js';
@@ -2,7 +2,6 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { isInLayout, packageRootIn, versionDir, switchCurrent, execStartLine } from './chunk-YYYXRJUW.js';
4
4
  import { RUNNER_DIR_MODE } from './chunk-ZM2IMMYN.js';
5
- import './chunk-Z5GEFMRW.js';
6
5
  import { realpathSync, readFileSync, mkdirSync, rmSync, cpSync, existsSync } from 'fs';
7
6
  import { dirname, sep, join } from 'path';
8
7
 
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { isLinkedRepository } from './chunk-D2KWEAN6.js';
3
+ import { isLinkedRepository } from './chunk-7DBI6SNT.js';
4
4
  import { readStatuslineState, STATE_FRESH_MS } from './chunk-F47JBIAW.js';
5
- import { resolveCredentials, getJson, WorkCommandError } from './chunk-RMAZ2NWA.js';
6
- import { sanitizeTerminal } from './chunk-WOBNRVQT.js';
7
- import { resolveRepoIdentity, compareVersions, SUPPORTED_VERSIONS } from './chunk-EX6M5JPR.js';
5
+ import { resolveCredentials, getJson, WorkCommandError } from './chunk-BNMCXPG7.js';
6
+ import { sanitizeTerminal } from './chunk-3DEVYTNQ.js';
7
+ import { resolveRepoIdentity, compareVersions, SUPPORTED_VERSIONS } from './chunk-ANK2B36Y.js';
8
8
  import { C } from './chunk-YRYMNBGQ.js';
9
9
  import { readSettings, settingsPath, isOurStatuslineSlot, statuslineWrappedOriginal, REGISTERED_EVENTS, isOurHookCommand, ourCommandBins, cliBinResolvable, ourDirectScriptBins, wrappedOriginalCommand } from './chunk-5QRP3MVS.js';
10
10
  import { readPin, readContextManifest, readDegraded, readSourceCache } from './chunk-WIOZAJ2W.js';
11
11
  import { defaultFetch } from './chunk-BCLJSQCV.js';
12
12
  import { resolveGatewayCredentials } from './chunk-BOLTI6LR.js';
13
13
  import { readCloudConfig } from './chunk-EABW6AJQ.js';
14
- import { sanitizeForContext, spoolDir, assessSyncHealth, readSyncStatus, SYNC_STALE_AFTER_MS, formatAgo, checkInstalledCliStaleness, workspaceRootOf, hookFiredMarkerPath, refreshCommand } from './chunk-UFFXNXLQ.js';
14
+ import { sanitizeForContext, spoolDir, assessSyncHealth, readSyncStatus, SYNC_STALE_AFTER_MS, formatAgo, checkInstalledCliStaleness, workspaceRootOf, hookFiredMarkerPath, refreshCommand } from './chunk-FA2BOX4O.js';
15
15
  import { deadHooksState, preparePackageManagerCommand, contextExcludesPresent } from './chunk-XCCLFVXM.js';
16
16
  import { findGitDirs } from './chunk-BJ3KTMDH.js';
17
17
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
@@ -1004,7 +1004,7 @@ function doctorExitCode(checks) {
1004
1004
 
1005
1005
  // src/doctor-report.ts
1006
1006
  async function gatherDoctorChecks() {
1007
- const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-XVJYZ4IQ.js');
1007
+ const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-HL2AIR3K.js');
1008
1008
  const configPath = defaultRunnerConfigPath();
1009
1009
  const checks = runDoctor({ runnerHost: hasRunnerBlock(configPath) });
1010
1010
  const credential = await credentialChecks();
@@ -1012,11 +1012,11 @@ async function gatherDoctorChecks() {
1012
1012
  if (credential[0]?.status === "pass") checks.push(...await githubAppChecks());
1013
1013
  const runnerVm = runnerVmOf(configPath);
1014
1014
  if (runnerVm !== void 0) {
1015
- const { realLima } = await import('./lima-XN65D7GN.js');
1015
+ const { realLima } = await import('./lima-7NPCNGWT.js');
1016
1016
  const { runnerVmCheck } = await import('./doctor-53F2PYY7.js');
1017
1017
  checks.push(runnerVmCheck(configPath, { lima: realLima(), home: homeFromEnv(), instance: runnerVm.instance }));
1018
1018
  } else if (hasRunnerBlock(configPath)) {
1019
- const { runnerHostChecks } = await import('./doctor-J3GIMR7K.js');
1019
+ const { runnerHostChecks } = await import('./doctor-VOMYOHQU.js');
1020
1020
  const hooks = (() => {
1021
1021
  try {
1022
1022
  return loadRunnerConfig(configPath).hooks;
@@ -1041,13 +1041,21 @@ async function gatherDoctorChecks() {
1041
1041
  })();
1042
1042
  const runner = (() => {
1043
1043
  try {
1044
- const { concurrency, build } = loadRunnerConfig(configPath);
1045
- return { concurrency, buildMemory: build?.memory ?? "8g" };
1044
+ const { concurrency, build, housekeeping } = loadRunnerConfig(configPath);
1045
+ return { concurrency, buildMemory: build?.memory ?? "8g", minFreeDisk: housekeeping?.minFreeDisk };
1046
1046
  } catch {
1047
1047
  return void 0;
1048
1048
  }
1049
1049
  })();
1050
- checks.push(...await runnerHostChecks({ configPath, hooks, gateway, repos, runner }));
1050
+ const service = await (async () => {
1051
+ const { existsSync: existsSync2 } = await import('fs');
1052
+ if (process.platform !== "linux" || !existsSync2("/run/systemd/system")) return void 0;
1053
+ const { inspectService, realInspectProbes } = await import('./inspect-XAUSB7L3.js');
1054
+ const { DEFAULT_UNIT_NAME } = await import('./install-DSUZE2TJ.js');
1055
+ const { dirname: dirname2 } = await import('path');
1056
+ return inspectService(DEFAULT_UNIT_NAME, dirname2(configPath), realInspectProbes());
1057
+ })();
1058
+ checks.push(...await runnerHostChecks({ configPath, hooks, gateway, repos, runner, service }));
1051
1059
  }
1052
1060
  return checks;
1053
1061
  }