@naxodev/apnea 0.1.0 → 0.2.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.
package/dist/cli.js CHANGED
@@ -35989,13 +35989,11 @@ var ProfileSchema = exports_Schema.Struct({
35989
35989
  var RoleBindingSchema = exports_Schema.Struct({
35990
35990
  profile: exports_Schema.String.check(exports_Schema.isMinLength(1))
35991
35991
  });
35992
- var PaneStyleSchema = exports_Schema.Literals(["regular", "floating"]);
35993
35992
  var GlobalConfigSchema = exports_Schema.Struct({
35994
35993
  profiles: exports_Schema.optional(exports_Schema.Record(exports_Schema.String, ProfileSchema)),
35995
35994
  roles: exports_Schema.optional(exports_Schema.Record(exports_Schema.String, RoleBindingSchema)),
35996
35995
  review_round_cap: exports_Schema.optional(exports_Schema.Number),
35997
- timeouts_ms: exports_Schema.optional(exports_Schema.Record(exports_Schema.String, exports_Schema.Number)),
35998
- pane_style: exports_Schema.optional(PaneStyleSchema)
35996
+ timeouts_ms: exports_Schema.optional(exports_Schema.Record(exports_Schema.String, exports_Schema.Number))
35999
35997
  });
36000
35998
  var PROJECT_KNOWN = new Set([
36001
35999
  "roles",
@@ -36015,8 +36013,7 @@ var ProjectConfigSchema = exports_Schema.Struct({
36015
36013
  roles: exports_Schema.optional(exports_Schema.Record(exports_Schema.String, RoleBindingSchema)),
36016
36014
  review_round_cap: exports_Schema.optional(exports_Schema.Number),
36017
36015
  timeouts_ms: exports_Schema.optional(exports_Schema.Record(exports_Schema.String, exports_Schema.Number)),
36018
- isolation: exports_Schema.optional(exports_Schema.Literal("shared_cwd")),
36019
- pane_style: exports_Schema.optional(PaneStyleSchema)
36016
+ isolation: exports_Schema.optional(exports_Schema.Literal("shared_cwd"))
36020
36017
  });
36021
36018
  function configFail(message, path2) {
36022
36019
  return exports_Result.fail(path2 !== undefined ? new ConfigError({ message, path: path2 }) : new ConfigError({ message }));
@@ -36032,6 +36029,9 @@ function decodeGlobalConfig(raw) {
36032
36029
  if (exports_Result.isFailure(objR))
36033
36030
  return configFail(objR.failure.message);
36034
36031
  const obj = objR.success;
36032
+ if ("pane_style" in obj && obj.pane_style !== "regular" && obj.pane_style !== "floating") {
36033
+ return configFail(`invalid legacy pane_style=${JSON.stringify(obj.pane_style)}; expected "regular" or "floating"`);
36034
+ }
36035
36035
  if ("isolation" in obj && obj.isolation !== undefined && obj.isolation !== "shared_cwd") {
36036
36036
  return configFail(`unimplemented config value isolation=${JSON.stringify(obj.isolation)} (v1 only supports shared_cwd or omit)`);
36037
36037
  }
@@ -36047,7 +36047,8 @@ function decodeGlobalConfig(raw) {
36047
36047
  }
36048
36048
  }
36049
36049
  }
36050
- const decoded = exports_Schema.decodeUnknownResult(GlobalConfigSchema)(obj);
36050
+ const { pane_style: _legacy, ...globalConfig } = obj;
36051
+ const decoded = exports_Schema.decodeUnknownResult(GlobalConfigSchema)(globalConfig);
36051
36052
  if (exports_Result.isFailure(decoded)) {
36052
36053
  return configFail(decoded.failure.message);
36053
36054
  }
@@ -36075,13 +36076,11 @@ function decodeGlobalConfig(raw) {
36075
36076
  timeouts[k] = v;
36076
36077
  }
36077
36078
  }
36078
- const pane_style = d.pane_style === "regular" || d.pane_style === "floating" ? d.pane_style : "regular";
36079
36079
  return exports_Result.succeed({
36080
36080
  profiles,
36081
36081
  roles,
36082
36082
  review_round_cap: typeof d.review_round_cap === "number" && d.review_round_cap >= 1 ? d.review_round_cap : 3,
36083
- timeouts_ms: timeouts,
36084
- pane_style
36083
+ timeouts_ms: timeouts
36085
36084
  });
36086
36085
  }
36087
36086
  function decodeProjectConfig(raw) {
@@ -36092,6 +36091,9 @@ function decodeProjectConfig(raw) {
36092
36091
  if (exports_Result.isFailure(objR))
36093
36092
  return configFail(objR.failure.message);
36094
36093
  const obj = objR.success;
36094
+ if ("pane_style" in obj && obj.pane_style !== "regular" && obj.pane_style !== "floating") {
36095
+ return configFail(`invalid legacy pane_style=${JSON.stringify(obj.pane_style)}; expected "regular" or "floating"`);
36096
+ }
36095
36097
  for (const key of Object.keys(obj)) {
36096
36098
  if (PROJECT_FORBIDDEN.has(key)) {
36097
36099
  return configFail(`project config must not set ${key} (binaries/profiles only allowed in global config)`);
@@ -36116,7 +36118,8 @@ function decodeProjectConfig(raw) {
36116
36118
  }
36117
36119
  }
36118
36120
  }
36119
- const decoded = exports_Schema.decodeUnknownResult(ProjectConfigSchema)(obj, {
36121
+ const { pane_style: _legacy, ...projectConfig } = obj;
36122
+ const decoded = exports_Schema.decodeUnknownResult(ProjectConfigSchema)(projectConfig, {
36120
36123
  onExcessProperty: "error"
36121
36124
  });
36122
36125
  if (exports_Result.isFailure(decoded)) {
@@ -36142,8 +36145,7 @@ function applyProjectConfig(cfg, overlay) {
36142
36145
  profiles: cfg.profiles,
36143
36146
  roles,
36144
36147
  review_round_cap: overlay.review_round_cap !== undefined && overlay.review_round_cap >= 1 ? overlay.review_round_cap : cfg.review_round_cap,
36145
- timeouts_ms: timeouts,
36146
- pane_style: overlay.pane_style !== undefined ? overlay.pane_style : cfg.pane_style
36148
+ timeouts_ms: timeouts
36147
36149
  };
36148
36150
  }
36149
36151
  function resolveRoleCmdResult(cfg, role, mode = ROLE_MODE[role]) {
@@ -36353,37 +36355,9 @@ var ConfigLive = exports_Layer.effect(Config, exports_Effect.gen(function* () {
36353
36355
  // extension/services/herdr.ts
36354
36356
  import { spawnSync } from "child_process";
36355
36357
  import * as fs3 from "fs";
36356
- import * as os2 from "os";
36357
36358
  import * as path3 from "path";
36358
36359
 
36359
36360
  // extension/domain/herdr.ts
36360
- function parseHerdrVersion(raw) {
36361
- const m = raw.match(/(\d+)\.(\d+)\.(\d+)/);
36362
- if (!m)
36363
- return null;
36364
- return [Number(m[1]), Number(m[2]), Number(m[3])];
36365
- }
36366
- function versionGte(a, b) {
36367
- const [a0, a1, a2] = a;
36368
- const [b0, b1, b2] = b;
36369
- if (a0 !== b0)
36370
- return a0 > b0;
36371
- if (a1 !== b1)
36372
- return a1 > b1;
36373
- return a2 >= b2;
36374
- }
36375
- function supportsFloating(version2) {
36376
- return version2 != null && versionGte(version2, [0, 7, 4]);
36377
- }
36378
- function effectivePaneStyle(configured, role) {
36379
- if (configured === "regular") {
36380
- return { style: "regular", effective: "regular" };
36381
- }
36382
- if (role === "planner" || role === "reviewer") {
36383
- return { style: "floating", effective: "floating" };
36384
- }
36385
- return { style: "regular", effective: "regular (interactive role)" };
36386
- }
36387
36361
  function shellJoin(parts) {
36388
36362
  return parts.map((p) => {
36389
36363
  if (p === "&&" || p === "|" || p === "exec" || p === "env")
@@ -36401,31 +36375,6 @@ function looksLikeShellOnly(names2) {
36401
36375
  return /^(zsh|bash|sh|fish)$/i.test(t) || t === "-zsh" || t === "-bash" || t === "-sh";
36402
36376
  });
36403
36377
  }
36404
- function parseFloatingExit(text) {
36405
- const t = text.trim();
36406
- const n = Number.parseInt(t, 10);
36407
- return Number.isFinite(n) ? n : null;
36408
- }
36409
- function floatingTaskScriptBody(opts) {
36410
- return [
36411
- "#!/bin/bash",
36412
- "set -uo pipefail",
36413
- `EXIT_FILE=${shellJoin([opts.exitFileAbs])}`,
36414
- "write_exit() {",
36415
- " local st=$?",
36416
- ` printf '%s
36417
- ' "$st" > "$EXIT_FILE" 2>/dev/null || true`,
36418
- "}",
36419
- "trap write_exit EXIT",
36420
- "trap 'exit 129' HUP",
36421
- "trap 'exit 130' INT",
36422
- "trap 'exit 143' TERM",
36423
- shellJoin(["cd", opts.root]),
36424
- shellJoin([...opts.resolvedCmd, "--", opts.prompt]),
36425
- ""
36426
- ].join(`
36427
- `);
36428
- }
36429
36378
 
36430
36379
  // extension/services/herdr.ts
36431
36380
  class Herdr extends exports_Context.Service()("apnea/Herdr") {
@@ -36489,27 +36438,6 @@ function resolveExecutable(bin, envPath = process.env.PATH) {
36489
36438
  }
36490
36439
  return null;
36491
36440
  }
36492
- function floatingPanePath(base2 = process.env.PATH ?? "", home = os2.homedir()) {
36493
- const extras = [
36494
- path3.join(home, ".local", "bin"),
36495
- path3.join(home, ".bun", "bin"),
36496
- "/opt/homebrew/bin",
36497
- "/usr/local/bin"
36498
- ];
36499
- const parts = base2.split(path3.delimiter).filter(Boolean);
36500
- const seen = new Set(parts);
36501
- for (const extra of extras) {
36502
- if (seen.has(extra))
36503
- continue;
36504
- try {
36505
- if (fs3.statSync(extra).isDirectory()) {
36506
- parts.push(extra);
36507
- seen.add(extra);
36508
- }
36509
- } catch {}
36510
- }
36511
- return parts.join(path3.delimiter);
36512
- }
36513
36441
  function herdrEnabledSync() {
36514
36442
  return process.env.HERDR_ENV === "1";
36515
36443
  }
@@ -36628,21 +36556,6 @@ function paneSendKeysSync(paneId, keys4) {
36628
36556
  throw new HerdrError({ message: `herdr pane send-keys failed: ${r.raw}` });
36629
36557
  }
36630
36558
  }
36631
- function herdrVersionSync() {
36632
- return parseHerdrVersion(herdrCli(["--version"]).raw);
36633
- }
36634
- function hasApneaPluginSync() {
36635
- const r = herdrCli(["plugin", "list", "--plugin", "apnea", "--json"]);
36636
- const json2 = r.json;
36637
- if (json2) {
36638
- const res = resultOf(json2);
36639
- const plugins = res?.plugins ?? [];
36640
- if (plugins.some((p) => p.plugin_id === "apnea" || p.id === "apnea")) {
36641
- return true;
36642
- }
36643
- }
36644
- return /"(?:plugin_id|id)"\s*:\s*"apnea"/.test(r.raw);
36645
- }
36646
36559
  function paneForegroundNamesSync(paneId) {
36647
36560
  try {
36648
36561
  const r = spawnSync("herdr", ["pane", "process-info", "--pane", paneId], {
@@ -36900,8 +36813,6 @@ var makeHerdrLive = (hostAdapter) => exports_Layer.effect(Herdr, exports_Effect.
36900
36813
  try: herdrAvailabilitySync,
36901
36814
  catch: toHerdrError
36902
36815
  }),
36903
- version: exports_Effect.sync(herdrVersionSync),
36904
- hasApneaPlugin: exports_Effect.sync(hasApneaPluginSync),
36905
36816
  paneGet: (paneId) => exports_Effect.sync(() => paneGetSync(paneId)),
36906
36817
  paneRun,
36907
36818
  paneReadRecent: (paneId) => exports_Effect.try({
@@ -36909,73 +36820,7 @@ var makeHerdrLive = (hostAdapter) => exports_Layer.effect(Herdr, exports_Effect.
36909
36820
  catch: toHerdrError
36910
36821
  }),
36911
36822
  paneForegroundNames: (paneId) => exports_Effect.sync(() => paneForegroundNamesSync(paneId)),
36912
- runInteractivePrompt: (...args2) => runInteractivePromptImpl(hostAdapter, ...args2),
36913
- writeFloatingTaskScript: (scriptAbs, root, cmd, prompt, exitFileAbs) => exports_Effect.try({
36914
- try: () => {
36915
- if (cmd.length === 0) {
36916
- throw new HerdrError({
36917
- message: "floating oneshot cmd is empty; set cmd_oneshot on the role profile"
36918
- });
36919
- }
36920
- const bin = cmd[0];
36921
- if (bin === undefined || bin === "") {
36922
- throw new HerdrError({
36923
- message: "floating oneshot binary is empty; set cmd_oneshot on the role profile"
36924
- });
36925
- }
36926
- const resolved = resolveExecutable(bin);
36927
- if (!resolved) {
36928
- throw new HerdrError({
36929
- message: `floating oneshot binary "${bin}" not found on PATH; use an absolute cmd_oneshot or set pane_style=regular`
36930
- });
36931
- }
36932
- const resolvedCmd = [resolved, ...cmd.slice(1)];
36933
- const body = floatingTaskScriptBody({
36934
- root,
36935
- resolvedCmd,
36936
- prompt,
36937
- exitFileAbs
36938
- });
36939
- fs3.writeFileSync(scriptAbs, body, "utf8");
36940
- fs3.chmodSync(scriptAbs, 493);
36941
- },
36942
- catch: toHerdrError
36943
- }),
36944
- openFloatingPane: (taskScriptAbs, _root) => exports_Effect.try({
36945
- try: () => {
36946
- const r = herdrCli([
36947
- "plugin",
36948
- "pane",
36949
- "open",
36950
- "--plugin",
36951
- "apnea",
36952
- "--entrypoint",
36953
- "worker",
36954
- "--placement",
36955
- "popup",
36956
- "--env",
36957
- `APNEA_TASK_SCRIPT=${taskScriptAbs}`,
36958
- "--env",
36959
- `PATH=${floatingPanePath()}`
36960
- ]);
36961
- if (!r.ok) {
36962
- const raw = r.raw.trim();
36963
- if (/popup already open/i.test(raw)) {
36964
- throw new HerdrError({
36965
- message: "floating popup already open \u2014 herdr allows only one; dismiss it or workflow_wait for the in-flight oneshot before dispatching again"
36966
- });
36967
- }
36968
- throw new HerdrError({
36969
- message: `herdr plugin pane open failed: ${raw || r.raw}`
36970
- });
36971
- }
36972
- },
36973
- catch: toHerdrError
36974
- }),
36975
- linkPlugin: (dir) => exports_Effect.sync(() => {
36976
- const r = herdrCli(["plugin", "link", dir]);
36977
- return { ok: r.ok, raw: r.raw };
36978
- })
36823
+ runInteractivePrompt: (...args2) => runInteractivePromptImpl(hostAdapter, ...args2)
36979
36824
  })));
36980
36825
  var HerdrLive = makeHerdrLive(neutralHostAdapter);
36981
36826
 
@@ -37020,7 +36865,6 @@ var RunStateSchema = exports_Schema.Struct({
37020
36865
  pending_role: exports_Schema.NullOr(RoleSchema),
37021
36866
  pending_pane_id: exports_Schema.optionalKey(exports_Schema.NullOr(exports_Schema.String)),
37022
36867
  pending_pane_label: exports_Schema.optionalKey(exports_Schema.NullOr(exports_Schema.String)),
37023
- pending_floating_exit: exports_Schema.optionalKey(exports_Schema.NullOr(exports_Schema.String)),
37024
36868
  pending_started_at: exports_Schema.optionalKey(exports_Schema.NullOr(exports_Schema.Number)),
37025
36869
  pending_deadline_ms: exports_Schema.optionalKey(exports_Schema.NullOr(exports_Schema.Number)),
37026
36870
  pending_nudged_at: exports_Schema.optionalKey(exports_Schema.NullOr(exports_Schema.Number)),
@@ -37034,6 +36878,12 @@ var RunStateSchema = exports_Schema.Struct({
37034
36878
  phase_package_rework: exports_Schema.optionalKey(exports_Schema.Boolean)
37035
36879
  });
37036
36880
  function decodeRunState(json2, path4 = "state.json") {
36881
+ if (json2 !== null && typeof json2 === "object" && !Array.isArray(json2) && "pending_floating_exit" in json2 && json2.pending_floating_exit !== null) {
36882
+ return exports_Result.fail(new StateCorrupt({
36883
+ path: path4,
36884
+ message: 'this run has an active legacy floating dispatch, but floating dispatch was removed; dismiss or terminate the old popup first, then run `apnea abandon` and `apnea start "<goal>"`'
36885
+ }));
36886
+ }
37037
36887
  const decoded = exports_Schema.decodeUnknownResult(RunStateSchema)(json2);
37038
36888
  if (exports_Result.isFailure(decoded)) {
37039
36889
  return exports_Result.fail(new StateCorrupt({
@@ -37057,7 +36907,6 @@ function decodeRunState(json2, path4 = "state.json") {
37057
36907
  pending_role: d.pending_role,
37058
36908
  pending_pane_id: d.pending_pane_id ?? null,
37059
36909
  pending_pane_label: d.pending_pane_label ?? null,
37060
- pending_floating_exit: d.pending_floating_exit ?? null,
37061
36910
  pending_started_at: d.pending_started_at ?? null,
37062
36911
  pending_deadline_ms: d.pending_deadline_ms ?? null,
37063
36912
  pending_nudged_at: d.pending_nudged_at ?? null,
@@ -37140,8 +36989,161 @@ var RunStoreLive = exports_Layer.effect(RunStore, exports_Effect.gen(function* (
37140
36989
  }));
37141
36990
 
37142
36991
  // extension/services/vcs.ts
37143
- import { spawnSync as spawnSync2 } from "child_process";
36992
+ import { spawn, spawnSync as spawnSync2 } from "child_process";
36993
+ import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
36994
+ import { tmpdir } from "os";
37144
36995
  import * as path5 from "path";
36996
+
36997
+ // extension/domain/verify-commands.ts
36998
+ function normalizeVerifySource(source) {
36999
+ return source.replace(/\r\n?/g, `
37000
+ `);
37001
+ }
37002
+ function atxHeading(line) {
37003
+ const match8 = /^ {0,3}(#{1,6})(?:[\t ]+(.*?)|[\t ]*)$/.exec(line);
37004
+ if (!match8)
37005
+ return null;
37006
+ return {
37007
+ rank: match8[1].length,
37008
+ text: (match8[2] ?? "").replace(/[\t ]+#+[\t ]*$/, "").trim()
37009
+ };
37010
+ }
37011
+ function boldHeading(line) {
37012
+ return /^ {0,3}\*\*(.+?)\*\*[\t ]*$/.exec(line)?.[1]?.trim() ?? null;
37013
+ }
37014
+ function scanMarkdown(text) {
37015
+ const fences = [];
37016
+ const headings = [];
37017
+ let open;
37018
+ let offset = 0;
37019
+ while (offset < text.length) {
37020
+ const newline = text.indexOf(`
37021
+ `, offset);
37022
+ const lineEnd = newline === -1 ? text.length : newline;
37023
+ const next = newline === -1 ? text.length : newline + 1;
37024
+ const line = text.slice(offset, lineEnd);
37025
+ if (open) {
37026
+ const closing = /^ {0,3}(`{3,}|~{3,})[\t ]*$/.exec(line)?.[1];
37027
+ if (closing?.[0] === open.marker && closing.length >= open.length) {
37028
+ fences.push({
37029
+ start: open.start,
37030
+ bodyStart: open.bodyStart,
37031
+ bodyEnd: offset,
37032
+ info: open.info,
37033
+ indent: open.indent
37034
+ });
37035
+ open = undefined;
37036
+ }
37037
+ } else {
37038
+ const opening = /^( {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
37039
+ const marker = opening?.[2];
37040
+ const info = opening?.[3] ?? "";
37041
+ if (marker && !(marker[0] === "`" && info.includes("`"))) {
37042
+ open = {
37043
+ marker: marker[0],
37044
+ length: marker.length,
37045
+ start: offset,
37046
+ bodyStart: next,
37047
+ info: info.trim(),
37048
+ indent: opening[1].length
37049
+ };
37050
+ } else {
37051
+ const atx = atxHeading(line);
37052
+ const bold = boldHeading(line);
37053
+ if (atx) {
37054
+ headings.push({
37055
+ start: offset,
37056
+ end: next,
37057
+ rank: atx.rank,
37058
+ text: atx.text
37059
+ });
37060
+ } else if (bold !== null) {
37061
+ headings.push({ start: offset, end: next, rank: null, text: bold });
37062
+ }
37063
+ }
37064
+ }
37065
+ offset = next;
37066
+ }
37067
+ return { fences, headings, unclosedFence: open !== undefined };
37068
+ }
37069
+ function toVerifyBlock(text, fence) {
37070
+ const language = fence.info.toLowerCase();
37071
+ if (language !== "bash" && language !== "sh" && language !== "shell") {
37072
+ return null;
37073
+ }
37074
+ const body = text.slice(fence.bodyStart, fence.bodyEnd);
37075
+ const source = fence.indent === 0 ? body : body.replace(new RegExp(`^ {0,${fence.indent}}`, "gm"), "");
37076
+ const hasExecutableLine = source.split(`
37077
+ `).some((line) => line.trim() !== "" && !line.trimStart().startsWith("#"));
37078
+ if (!hasExecutableLine)
37079
+ return null;
37080
+ return {
37081
+ interpreter: language === "sh" ? "sh" : "bash",
37082
+ source
37083
+ };
37084
+ }
37085
+ function isShellFence(fence) {
37086
+ return /^(?:bash|sh|shell)$/i.test(fence.info);
37087
+ }
37088
+ function endsInContinuation(line) {
37089
+ const run2 = /(\\+)$/.exec(line);
37090
+ return run2 !== null && run2[1].length % 2 === 1;
37091
+ }
37092
+ function extractLegacyBlocks(text) {
37093
+ const blocks = [];
37094
+ const rawLines = text.split(`
37095
+ `);
37096
+ for (let i = 0;i < rawLines.length; i++) {
37097
+ const match8 = rawLines[i].match(/^\s*(?:\$\s+)?((?:test |node |npm |bun |bunx |chmod |head ).+)$/);
37098
+ if (!match8)
37099
+ continue;
37100
+ let source = match8[1];
37101
+ while (endsInContinuation(source) && i + 1 < rawLines.length) {
37102
+ source = source.slice(0, -1) + rawLines[++i];
37103
+ }
37104
+ if (endsInContinuation(source))
37105
+ source = source.slice(0, -1);
37106
+ blocks.push({ interpreter: "bash", source: source.trim() });
37107
+ }
37108
+ return blocks;
37109
+ }
37110
+ function extractVerifyBlocks(phasePackageText) {
37111
+ const text = normalizeVerifySource(phasePackageText);
37112
+ const { fences, headings, unclosedFence } = scanMarkdown(text);
37113
+ if (unclosedFence)
37114
+ return [];
37115
+ const verifyHeading = headings.find((heading) => heading.text.toLowerCase() === "verify commands");
37116
+ if (verifyHeading) {
37117
+ const nextHeading = headings.find((heading) => heading.start >= verifyHeading.end && (heading.rank === null || verifyHeading.rank === null || heading.rank !== null && heading.rank <= verifyHeading.rank));
37118
+ const sectionEnd = nextHeading?.start ?? text.length;
37119
+ const sectionFences = fences.filter((fence) => fence.start >= verifyHeading.end && fence.start < sectionEnd);
37120
+ const blocks = sectionFences.filter(isShellFence).map((fence) => toVerifyBlock(text, fence)).filter((block) => block !== null);
37121
+ return sectionFences.length > 0 ? blocks : extractLegacyBlocks(text.slice(verifyHeading.end, sectionEnd));
37122
+ }
37123
+ const shellFences = fences.filter(isShellFence);
37124
+ if (shellFences.length > 0) {
37125
+ const block = toVerifyBlock(text, shellFences[shellFences.length - 1]);
37126
+ return block ? [block] : [];
37127
+ }
37128
+ return fences.length === 0 ? extractLegacyBlocks(text) : [];
37129
+ }
37130
+ function formatVerifyBlock(block) {
37131
+ const source = normalizeVerifySource(block.source);
37132
+ const body = source.endsWith(`
37133
+ `) ? source.slice(0, -1) : source;
37134
+ const displayedSource = body.split(`
37135
+ `).map((line) => `| ${line}`).join(`
37136
+ `);
37137
+ return `${block.interpreter} -e [verification block]
37138
+ ${displayedSource}`;
37139
+ }
37140
+ function formatVerifyCommand(block) {
37141
+ const source = normalizeVerifySource(block.source);
37142
+ const quotedSource = `'${source.replaceAll("'", `'"'"'`)}'`;
37143
+ return `${block.interpreter} -e -c ${quotedSource}`;
37144
+ }
37145
+
37146
+ // extension/services/vcs.ts
37145
37147
  class Vcs extends exports_Context.Service()("apnea/Vcs") {
37146
37148
  }
37147
37149
  function run2(cmd, args2, cwd2) {
@@ -37157,6 +37159,248 @@ function run2(cmd, args2, cwd2) {
37157
37159
  code: r.status ?? 1
37158
37160
  };
37159
37161
  }
37162
+ function verificationError(error2, temporaryDirectory) {
37163
+ const message = error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2);
37164
+ return temporaryDirectory ? message.replaceAll(temporaryDirectory, "[temporary verification directory]") : message;
37165
+ }
37166
+ var VERIFY_LOG_LIMIT = 10 * 1024 * 1024;
37167
+ var VERIFY_KILL_CLOSE_GRACE_MS = 2500;
37168
+ var VERIFY_RESULT_RESERVE = 2048;
37169
+ var VERIFY_WRAPPER_SOURCE = `exec 2>&1
37170
+ exec "$1" -e "$2"
37171
+ `;
37172
+ var VERIFY_DISPLAY_LIMIT_NOTICE = `verification log limit of ${VERIFY_LOG_LIMIT} bytes would be exceeded by the verification block display; block was not executed`;
37173
+ var VERIFY_LOG_LIMIT_NOTICE = `verification log limit of ${VERIFY_LOG_LIMIT} bytes reached; output was truncated and verification stopped`;
37174
+ var VERIFY_LIMIT_NOTICE_RESERVE = 1 + Math.max(Buffer.byteLength(VERIFY_DISPLAY_LIMIT_NOTICE), Buffer.byteLength(VERIFY_LOG_LIMIT_NOTICE));
37175
+ function utf8BytesAfterAppend(usedBytes, limitBytes, text) {
37176
+ const nextBytes = usedBytes + Buffer.byteLength(text);
37177
+ return nextBytes <= limitBytes ? nextBytes : null;
37178
+ }
37179
+
37180
+ class VerificationLog {
37181
+ limit;
37182
+ #chunks = [];
37183
+ #contentLimit;
37184
+ #bytes = 0;
37185
+ #limited = false;
37186
+ constructor(limit) {
37187
+ this.limit = limit;
37188
+ this.#contentLimit = Math.max(0, limit - VERIFY_LIMIT_NOTICE_RESERVE);
37189
+ }
37190
+ get remaining() {
37191
+ return this.#contentLimit - this.#bytes;
37192
+ }
37193
+ canAppendBytes(bytes) {
37194
+ return bytes <= this.remaining;
37195
+ }
37196
+ append(text) {
37197
+ const nextBytes = utf8BytesAfterAppend(this.#bytes, this.#contentLimit, text);
37198
+ if (nextBytes === null)
37199
+ return false;
37200
+ this.#chunks.push(text);
37201
+ this.#bytes = nextBytes;
37202
+ return true;
37203
+ }
37204
+ addLimitNotice(notice) {
37205
+ if (this.#limited)
37206
+ return;
37207
+ this.#limited = true;
37208
+ const previous = this.#chunks.at(-1);
37209
+ if (this.#bytes > 0 && !previous?.endsWith(`
37210
+ `)) {
37211
+ this.#chunks.push(`
37212
+ `);
37213
+ this.#bytes += 1;
37214
+ }
37215
+ this.#chunks.push(notice);
37216
+ this.#bytes += Buffer.byteLength(notice);
37217
+ }
37218
+ toString() {
37219
+ return this.#chunks.join("").trimEnd();
37220
+ }
37221
+ }
37222
+ function verifyBlockDisplayByteLength(block) {
37223
+ const source = block.source;
37224
+ const bodyEnd = source.endsWith(`
37225
+ `) ? source.length - 1 : source.length;
37226
+ let lineCount = 1;
37227
+ for (let index2 = 0;index2 < bodyEnd; index2++) {
37228
+ if (source.charCodeAt(index2) === 10)
37229
+ lineCount += 1;
37230
+ }
37231
+ const bodyBytes = Buffer.byteLength(source) - (bodyEnd < source.length ? 1 : 0);
37232
+ return Buffer.byteLength(`${block.interpreter} -e [verification block]
37233
+ `) + bodyBytes + lineCount * 2;
37234
+ }
37235
+ async function taskkillTree(pid) {
37236
+ return new Promise((resolve6) => {
37237
+ let settled = false;
37238
+ let killer;
37239
+ const finish = (ok2) => {
37240
+ if (settled)
37241
+ return;
37242
+ settled = true;
37243
+ clearTimeout(timer);
37244
+ resolve6(ok2);
37245
+ };
37246
+ try {
37247
+ killer = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], {
37248
+ stdio: "ignore",
37249
+ windowsHide: true
37250
+ });
37251
+ } catch {
37252
+ resolve6(false);
37253
+ return;
37254
+ }
37255
+ const timer = setTimeout(() => {
37256
+ try {
37257
+ killer.kill("SIGKILL");
37258
+ } catch {}
37259
+ finish(false);
37260
+ }, 2000);
37261
+ killer.once("error", () => finish(false));
37262
+ killer.once("close", (code) => finish(code === 0));
37263
+ });
37264
+ }
37265
+ function snapshotProcessDescendants(rootPid) {
37266
+ const snapshot = spawnSync2("ps", ["-axo", "pid=,ppid="], {
37267
+ encoding: "utf8",
37268
+ maxBuffer: 10 * 1024 * 1024
37269
+ });
37270
+ if (snapshot.status !== 0 || snapshot.error)
37271
+ return [];
37272
+ const children = new Map;
37273
+ for (const line of (snapshot.stdout ?? "").split(`
37274
+ `)) {
37275
+ const match8 = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
37276
+ if (!match8)
37277
+ continue;
37278
+ const pid = Number(match8[1]);
37279
+ const parentPid = Number(match8[2]);
37280
+ const siblings = children.get(parentPid);
37281
+ if (siblings)
37282
+ siblings.push(pid);
37283
+ else
37284
+ children.set(parentPid, [pid]);
37285
+ }
37286
+ const descendants = [];
37287
+ const pending = [...children.get(rootPid) ?? []];
37288
+ for (let index2 = 0;index2 < pending.length; index2++) {
37289
+ const pid = pending[index2];
37290
+ descendants.push(pid);
37291
+ pending.push(...children.get(pid) ?? []);
37292
+ }
37293
+ return descendants;
37294
+ }
37295
+ async function killProcessTree(child) {
37296
+ const pid = child.pid;
37297
+ if (process.platform === "win32" && pid !== undefined) {
37298
+ if (await taskkillTree(pid))
37299
+ return;
37300
+ } else if (pid !== undefined) {
37301
+ const descendants = snapshotProcessDescendants(pid);
37302
+ try {
37303
+ process.kill(-pid, "SIGKILL");
37304
+ } catch {}
37305
+ for (const descendantPid of descendants) {
37306
+ try {
37307
+ process.kill(descendantPid, "SIGKILL");
37308
+ } catch {}
37309
+ }
37310
+ }
37311
+ try {
37312
+ child.kill("SIGKILL");
37313
+ } catch {}
37314
+ }
37315
+ function runVerificationProcess(wrapper, interpreter, script4, cwd2, timeoutMs, outputLimit) {
37316
+ return new Promise((resolve6) => {
37317
+ let child;
37318
+ try {
37319
+ child = spawn("sh", [wrapper, interpreter, script4], {
37320
+ cwd: cwd2,
37321
+ detached: process.platform !== "win32",
37322
+ stdio: ["ignore", "pipe", "ignore"],
37323
+ windowsHide: true
37324
+ });
37325
+ } catch (error2) {
37326
+ resolve6({
37327
+ code: 1,
37328
+ output: "",
37329
+ error: verificationError(error2)
37330
+ });
37331
+ return;
37332
+ }
37333
+ const output = [];
37334
+ let outputSize = 0;
37335
+ let outputExceeded = false;
37336
+ let processError;
37337
+ let termination;
37338
+ let timeout3;
37339
+ let postKillCompletion;
37340
+ let settled = false;
37341
+ const terminate = (message) => {
37342
+ if (settled)
37343
+ return;
37344
+ processError ??= message;
37345
+ if (termination)
37346
+ return;
37347
+ termination = killProcessTree(child);
37348
+ postKillCompletion = setTimeout(() => {
37349
+ child.stdout?.destroy();
37350
+ finish(1);
37351
+ }, VERIFY_KILL_CLOSE_GRACE_MS);
37352
+ };
37353
+ const capture = (chunk) => {
37354
+ if (outputExceeded)
37355
+ return;
37356
+ const available = outputLimit - outputSize;
37357
+ if (chunk.length > available) {
37358
+ if (available > 0)
37359
+ output.push(chunk.subarray(0, available));
37360
+ outputSize = outputLimit;
37361
+ outputExceeded = true;
37362
+ terminate(`verification output exceeded ${outputLimit} bytes`);
37363
+ return;
37364
+ }
37365
+ output.push(chunk);
37366
+ outputSize += chunk.length;
37367
+ };
37368
+ const onStdout = (chunk) => capture(chunk);
37369
+ const onError3 = (error2) => {
37370
+ terminate(`verification process error: ${verificationError(error2)}`);
37371
+ };
37372
+ const finish = (code) => {
37373
+ if (settled)
37374
+ return;
37375
+ settled = true;
37376
+ if (timeout3)
37377
+ clearTimeout(timeout3);
37378
+ if (postKillCompletion)
37379
+ clearTimeout(postKillCompletion);
37380
+ child.stdout?.off("data", onStdout);
37381
+ child.off("error", onError3);
37382
+ child.off("close", onClose);
37383
+ resolve6({
37384
+ code,
37385
+ output: Buffer.concat(output).toString("utf8"),
37386
+ ...processError === undefined ? {} : { error: processError }
37387
+ });
37388
+ };
37389
+ const onClose = (code) => {
37390
+ if (termination) {
37391
+ termination.then(() => finish(code ?? 1), () => finish(code ?? 1));
37392
+ } else {
37393
+ finish(code ?? 1);
37394
+ }
37395
+ };
37396
+ child.stdout?.on("data", onStdout);
37397
+ child.once("error", onError3);
37398
+ child.once("close", onClose);
37399
+ timeout3 = setTimeout(() => {
37400
+ terminate(`verification timed out after ${timeoutMs}ms`);
37401
+ }, timeoutMs);
37402
+ });
37403
+ }
37160
37404
  function filterAppPaths(summary) {
37161
37405
  return summary.split(/\r?\n/).filter((line) => {
37162
37406
  const t = line.trim();
@@ -37262,33 +37506,93 @@ var VcsLive = exports_Layer.effect(Vcs, exports_Effect.gen(function* () {
37262
37506
  run2("jj", ["bookmark", "create", name, "-r", "@-"], root);
37263
37507
  }
37264
37508
  });
37265
- const runVerify = (root, commands2, timeoutMs) => exports_Effect.sync(() => {
37266
- const lines = [];
37267
- for (const cmd of commands2) {
37268
- lines.push(`$ ${cmd}`);
37269
- const r = spawnSync2("bash", ["-lc", cmd], {
37270
- cwd: root,
37509
+ const runVerify = (root, blocks, timeoutMs) => exports_Effect.promise(async () => {
37510
+ const log2 = new VerificationLog(VERIFY_LOG_LIMIT);
37511
+ let temporaryDirectory;
37512
+ let ok2 = true;
37513
+ let operation = "create temporary verification directory";
37514
+ try {
37515
+ temporaryDirectory = mkdtempSync(path5.join(tmpdir(), "apnea-verify-"));
37516
+ const wrapper = path5.join(temporaryDirectory, "run-block.sh");
37517
+ operation = "write verification wrapper";
37518
+ writeFileSync2(wrapper, VERIFY_WRAPPER_SOURCE, {
37271
37519
  encoding: "utf8",
37272
- timeout: timeoutMs,
37273
- maxBuffer: 10 * 1024 * 1024
37520
+ mode: 384
37274
37521
  });
37275
- const out = `${r.stdout ?? ""}${r.stderr ?? ""}`.trimEnd();
37276
- if (out)
37277
- lines.push(out);
37278
- lines.push(`exit=${r.status ?? 1}`);
37279
- if (r.error) {
37280
- lines.push(String(r.error));
37281
- return { ok: false, log: lines.join(`
37282
- `) };
37522
+ for (const [index2, block] of blocks.entries()) {
37523
+ const source = normalizeVerifySource(block.source);
37524
+ const normalizedBlock = { ...block, source };
37525
+ const script4 = path5.join(temporaryDirectory, `block-${index2 + 1}.${block.interpreter}`);
37526
+ const displayBytes = 2 + verifyBlockDisplayByteLength(normalizedBlock) + 1;
37527
+ if (!log2.canAppendBytes(displayBytes)) {
37528
+ log2.addLimitNotice(VERIFY_DISPLAY_LIMIT_NOTICE);
37529
+ ok2 = false;
37530
+ break;
37531
+ }
37532
+ log2.append(`$ ${formatVerifyBlock(normalizedBlock)}
37533
+ `);
37534
+ operation = `write ${block.interpreter} verification block`;
37535
+ writeFileSync2(script4, source, {
37536
+ encoding: "utf8",
37537
+ mode: 384
37538
+ });
37539
+ operation = `run ${block.interpreter} verification block`;
37540
+ const result3 = await runVerificationProcess(wrapper, block.interpreter, script4, root, timeoutMs, Math.max(0, log2.remaining - VERIFY_RESULT_RESERVE));
37541
+ const output = verificationError(result3.output.trimEnd(), temporaryDirectory);
37542
+ if (output && !log2.append(`${output}
37543
+ `)) {
37544
+ log2.addLimitNotice(VERIFY_LOG_LIMIT_NOTICE);
37545
+ ok2 = false;
37546
+ break;
37547
+ }
37548
+ if (!log2.append(`exit=${result3.code}
37549
+ `)) {
37550
+ log2.addLimitNotice(VERIFY_LOG_LIMIT_NOTICE);
37551
+ ok2 = false;
37552
+ break;
37553
+ }
37554
+ if (result3.error) {
37555
+ const error2 = verificationError(result3.error, temporaryDirectory);
37556
+ if (!log2.append(`${error2}
37557
+ `)) {
37558
+ log2.addLimitNotice(VERIFY_LOG_LIMIT_NOTICE);
37559
+ }
37560
+ ok2 = false;
37561
+ break;
37562
+ }
37563
+ if (result3.code !== 0) {
37564
+ ok2 = false;
37565
+ break;
37566
+ }
37567
+ if (index2 < blocks.length - 1 && !log2.append(`
37568
+ `)) {
37569
+ log2.addLimitNotice(VERIFY_LOG_LIMIT_NOTICE);
37570
+ ok2 = false;
37571
+ break;
37572
+ }
37283
37573
  }
37284
- if (r.status !== 0) {
37285
- return { ok: false, log: lines.join(`
37286
- `) };
37574
+ } catch (error2) {
37575
+ const message = `${operation} failed: ${verificationError(error2, temporaryDirectory)}
37576
+ `;
37577
+ if (!log2.append(message)) {
37578
+ log2.addLimitNotice(VERIFY_LOG_LIMIT_NOTICE);
37579
+ }
37580
+ ok2 = false;
37581
+ } finally {
37582
+ if (temporaryDirectory) {
37583
+ try {
37584
+ rmSync2(temporaryDirectory, { recursive: true, force: true });
37585
+ } catch (error2) {
37586
+ const message = `clean up temporary verification directory failed: ${verificationError(error2, temporaryDirectory)}
37587
+ `;
37588
+ if (!log2.append(message)) {
37589
+ log2.addLimitNotice(VERIFY_LOG_LIMIT_NOTICE);
37590
+ }
37591
+ ok2 = false;
37592
+ }
37287
37593
  }
37288
- lines.push("");
37289
37594
  }
37290
- return { ok: true, log: lines.join(`
37291
- `) };
37595
+ return { ok: ok2, log: log2.toString() };
37292
37596
  });
37293
37597
  return Vcs.of({
37294
37598
  detect,
@@ -37455,85 +37759,6 @@ function toolAllowed(step, tool) {
37455
37759
  return exports_Result.succeed(undefined);
37456
37760
  }
37457
37761
 
37458
- // extension/domain/verify-commands.ts
37459
- function endsInContinuation(line) {
37460
- const run3 = /(\\+)$/.exec(line);
37461
- return run3 !== null && run3[1].length % 2 === 1;
37462
- }
37463
- function logicalCommands(lines) {
37464
- const out = [];
37465
- let acc = "";
37466
- let pending = false;
37467
- for (const line of lines) {
37468
- const isCommentish = line.trim().startsWith("#");
37469
- if (!pending && isCommentish)
37470
- continue;
37471
- if (pending && isCommentish && (/\s$/.test(acc) || /^\s/.test(line))) {
37472
- out.push(acc);
37473
- acc = "";
37474
- pending = false;
37475
- continue;
37476
- }
37477
- const continues = endsInContinuation(line);
37478
- const text = continues ? line.slice(0, -1) : line;
37479
- acc = pending ? acc + text : text;
37480
- pending = continues;
37481
- if (!pending) {
37482
- out.push(acc);
37483
- acc = "";
37484
- }
37485
- }
37486
- if (pending)
37487
- out.push(acc);
37488
- return out;
37489
- }
37490
- function toCommands(lines) {
37491
- const cmds = [];
37492
- for (const joined of logicalCommands(lines)) {
37493
- const t = joined.trim();
37494
- if (t)
37495
- cmds.push(t);
37496
- }
37497
- return cmds;
37498
- }
37499
- function commandsFromFenceBody(body) {
37500
- return toCommands(body.split(/\r?\n/));
37501
- }
37502
- function extractVerifyCommands(phasePackageText) {
37503
- const section = phasePackageText.match(/(?:^|\n)(?:#{1,6}\s*|\*\*)Verify commands(?:\*\*)?\s*\r?\n([\s\S]*?)(?=\n#{1,6}\s|\n\*\*[A-Z]|$)/i);
37504
- if (section) {
37505
- const fence = section[1].match(/```(?:sh|bash|shell)\r?\n([\s\S]*?)```/i);
37506
- if (fence) {
37507
- const cmds2 = commandsFromFenceBody(fence[1]);
37508
- if (cmds2.length)
37509
- return cmds2;
37510
- }
37511
- }
37512
- const all5 = [
37513
- ...phasePackageText.matchAll(/```(?:sh|bash|shell)\r?\n([\s\S]*?)```/gi)
37514
- ];
37515
- for (let i = all5.length - 1;i >= 0; i--) {
37516
- const cmds2 = commandsFromFenceBody(all5[i][1]);
37517
- if (cmds2.length)
37518
- return cmds2;
37519
- }
37520
- const cmds = [];
37521
- const rawLines = phasePackageText.split(/\r?\n/);
37522
- for (let i = 0;i < rawLines.length; i++) {
37523
- const m = rawLines[i].match(/^\s*(?:\$\s+)?((?:test |node |npm |bun |bunx |chmod |head ).+)$/);
37524
- if (!m)
37525
- continue;
37526
- let cmd = m[1];
37527
- while (endsInContinuation(cmd) && i + 1 < rawLines.length) {
37528
- cmd = cmd.slice(0, -1) + rawLines[++i];
37529
- }
37530
- if (endsInContinuation(cmd))
37531
- cmd = cmd.slice(0, -1);
37532
- cmds.push(cmd.trim());
37533
- }
37534
- return cmds;
37535
- }
37536
-
37537
37762
  // extension/workflows/commit.ts
37538
37763
  var commitWorkflow = (params, root) => exports_Effect.gen(function* () {
37539
37764
  const store = yield* RunStore;
@@ -37581,8 +37806,8 @@ var commitWorkflow = (params, root) => exports_Effect.gen(function* () {
37581
37806
  });
37582
37807
  }
37583
37808
  const pkgText = yield* fs4.readFile(pkgAbs);
37584
- const cmds = extractVerifyCommands(pkgText);
37585
- if (!cmds.length) {
37809
+ const blocks = extractVerifyBlocks(pkgText);
37810
+ if (!blocks.length) {
37586
37811
  return yield* new ArtifactInvalid({
37587
37812
  artifact: pkgRel,
37588
37813
  message: "no verify commands found in phase package (need ```sh block)"
@@ -37590,14 +37815,14 @@ var commitWorkflow = (params, root) => exports_Effect.gen(function* () {
37590
37815
  }
37591
37816
  const cfg = yield* config.load(root);
37592
37817
  const verifyTimeout = cfg.timeouts_ms.verify ?? 900000;
37593
- const verify = yield* vcs.runVerify(root, cmds, verifyTimeout);
37818
+ const verify = yield* vcs.runVerify(root, blocks, verifyTimeout);
37594
37819
  const vlog = path6.join(path6.dirname(reviewAbs), "verify.log");
37595
37820
  yield* fs4.mkdir(path6.dirname(vlog), { recursive: true });
37596
37821
  yield* fs4.writeFile(vlog, `${verify.log}
37597
37822
  `);
37598
37823
  if (!verify.ok) {
37599
37824
  return yield* new VerifyFailed({
37600
- commands: cmds,
37825
+ commands: blocks.map(formatVerifyCommand),
37601
37826
  outputs: [verify.log.slice(-2000)],
37602
37827
  verify_log: rel(vlog, root)
37603
37828
  });
@@ -37860,50 +38085,11 @@ On rework, read latest code-review and fix.`;
37860
38085
  details: { role, tried: briefCandidates }
37861
38086
  });
37862
38087
  }
37863
- const paneStyle = effectivePaneStyle(cfg.pane_style, role);
37864
- let roleCmd = null;
37865
- let profileFingerprint = null;
37866
- if (herdrAvailability !== "unavailable") {
37867
- if (paneStyle.style === "floating") {
37868
- const version2 = yield* herdr.version;
37869
- if (!supportsFloating(version2)) {
37870
- return yield* new HerdrError({
37871
- message: "floating panes need herdr >= 0.7.4 \u2014 run `herdr update`, or set pane_style=regular"
37872
- });
37873
- }
37874
- if (!(yield* herdr.hasApneaPlugin)) {
37875
- return yield* new HerdrError({
37876
- message: `apnea herdr plugin not linked. Run /apnea setup, or: herdr plugin link ${livePackageRoot}/herdr-plugin`
37877
- });
37878
- }
37879
- if (state.pending_floating_exit) {
37880
- const prevExitAbs = abs2(state.pending_floating_exit, root);
37881
- if (!(yield* fs4.exists(prevExitAbs))) {
37882
- return yield* new GateRefused({
37883
- gate: "floating_in_flight",
37884
- message: "floating oneshot already in flight (popup still open). Call workflow_wait, or dismiss the popup and re-dispatch after it exits",
37885
- details: {
37886
- pending_artifact: state.pending_artifact,
37887
- pending_floating_exit: state.pending_floating_exit
37888
- }
37889
- });
37890
- }
37891
- }
37892
- const cmdResult = yield* exports_Effect.result(config.resolveRoleCmd(cfg, role, "oneshot"));
37893
- if (exports_Result.isFailure(cmdResult)) {
37894
- return yield* new HerdrError({
37895
- message: `floating dispatch requires cmd_oneshot on the role profile: ${cmdResult.failure.message}`
37896
- });
37897
- }
37898
- roleCmd = cmdResult.success;
37899
- } else {
37900
- roleCmd = yield* config.resolveRoleCmd(cfg, role, "interactive");
37901
- profileFingerprint = JSON.stringify([
37902
- cfg.roles[role]?.profile ?? null,
37903
- roleCmd
37904
- ]);
37905
- }
37906
- }
38088
+ const roleCmd = yield* config.resolveRoleCmd(cfg, role, "interactive");
38089
+ const profileFingerprint = JSON.stringify([
38090
+ cfg.roles[role]?.profile ?? null,
38091
+ roleCmd
38092
+ ]);
37907
38093
  if (role === "reviewer") {
37908
38094
  state.reviewer_tree_fingerprint = yield* vcsSvc.treeFingerprint(root, state.vcs);
37909
38095
  }
@@ -37943,9 +38129,7 @@ On rework, read latest code-review and fix.`;
37943
38129
  ].join(`
37944
38130
  `);
37945
38131
  let launch2 = {
37946
- mode: ROLE_MODE[role],
37947
- pane_style: cfg.pane_style,
37948
- pane_style_effective: paneStyle.effective
38132
+ mode: ROLE_MODE[role]
37949
38133
  };
37950
38134
  const rollbackLaunch = (restoreState = true) => exports_Effect.gen(function* () {
37951
38135
  const errors4 = [];
@@ -37956,8 +38140,6 @@ On rework, read latest code-review and fix.`;
37956
38140
  }
37957
38141
  });
37958
38142
  yield* attempt("remove task", fs4.remove(taskFile));
37959
- yield* attempt("remove floating script", fs4.remove(taskFile.replace(/\.md$/, ".sh")));
37960
- yield* attempt("remove floating exit marker", fs4.remove(taskFile.replace(/\.md$/, ".exit")));
37961
38143
  yield* attempt("remove replacement artifact", fs4.remove(artifactAbs));
37962
38144
  if (backupAbs != null) {
37963
38145
  yield* attempt("restore prior artifact", fs4.rename(backupAbs, artifactAbs));
@@ -37980,7 +38162,6 @@ On rework, read latest code-review and fix.`;
37980
38162
  markPending(preparedAt);
37981
38163
  state.pending_pane_id = null;
37982
38164
  state.pending_pane_label = null;
37983
- state.pending_floating_exit = null;
37984
38165
  const preparedState = yield* exports_Effect.exit(store.save(state, root));
37985
38166
  if (exports_Exit.isFailure(preparedState)) {
37986
38167
  const persistenceError = new HerdrError({
@@ -38002,104 +38183,58 @@ On rework, read latest code-review and fix.`;
38002
38183
  next: "workflow_wait"
38003
38184
  }, ["workflow_wait"]);
38004
38185
  }
38005
- if (paneStyle.style === "floating") {
38006
- const cmd = roleCmd;
38007
- const scriptAbs = taskFile.replace(/\.md$/, ".sh");
38008
- const exitAbs = taskFile.replace(/\.md$/, ".exit");
38009
- yield* fs4.remove(exitAbs);
38010
- const scriptWritten = yield* exports_Effect.result(herdr.writeFloatingTaskScript(scriptAbs, root, cmd, prompt, exitAbs));
38011
- if (exports_Result.isFailure(scriptWritten)) {
38012
- const rollbackErrors = yield* rollbackLaunch();
38013
- return yield* herdrAfterRollback(scriptWritten.failure, {
38014
- task_attempted: taskRef.task,
38015
- artifact: artifactRel
38016
- }, rollbackErrors);
38017
- }
38018
- state.pending_pane_id = null;
38019
- state.pending_pane_label = null;
38020
- state.pending_floating_exit = rel(exitAbs, root);
38021
- yield* store.save(state, root);
38022
- const opened = yield* exports_Effect.result(herdr.openFloatingPane(scriptAbs, root));
38023
- if (exports_Result.isFailure(opened)) {
38186
+ const cmd = roleCmd;
38187
+ const remembered = state.role_panes[role] ?? null;
38188
+ const prefer = remembered?.profile_fingerprint === profileFingerprint ? remembered : null;
38189
+ const launched = yield* exports_Effect.result(herdr.runInteractivePrompt(role, cmd, prompt, prefer));
38190
+ if (exports_Result.isFailure(launched)) {
38191
+ if (launched.failure.details?.delivery === "unknown") {
38192
+ const paneId = String(launched.failure.details.pane_id);
38193
+ const paneLabel = String(launched.failure.details.pane_label);
38194
+ state.pending_pane_id = paneId;
38195
+ state.pending_pane_label = paneLabel;
38196
+ state.role_panes[role] = {
38197
+ pane_id: paneId,
38198
+ label: paneLabel,
38199
+ profile_fingerprint: profileFingerprint
38200
+ };
38201
+ yield* store.save(state, root);
38024
38202
  return yield* new HerdrError({
38025
- message: opened.failure.message,
38026
- ...opened.failure.command !== undefined ? { command: opened.failure.command } : {},
38203
+ message: launched.failure.message,
38204
+ ...launched.failure.command !== undefined ? { command: launched.failure.command } : {},
38027
38205
  details: {
38028
- ...opened.failure.details ?? {},
38029
- delivery: "unknown",
38206
+ ...launched.failure.details ?? {},
38030
38207
  task_attempted: taskRef.task,
38031
38208
  artifact: artifactRel,
38032
38209
  pending_preserved: true
38033
38210
  }
38034
38211
  });
38035
38212
  }
38036
- launch2 = {
38037
- mode: "oneshot",
38038
- pane_style: cfg.pane_style,
38039
- pane_style_effective: "floating",
38040
- script: rel(scriptAbs, root),
38041
- exit: rel(exitAbs, root),
38042
- cmd,
38043
- prompt
38044
- };
38045
- } else {
38046
- const cmd = roleCmd;
38047
- const remembered = state.role_panes[role] ?? null;
38048
- const prefer = remembered?.profile_fingerprint === profileFingerprint ? remembered : null;
38049
- const launched = yield* exports_Effect.result(herdr.runInteractivePrompt(role, cmd, prompt, prefer));
38050
- if (exports_Result.isFailure(launched)) {
38051
- if (launched.failure.details?.delivery === "unknown") {
38052
- const paneId = String(launched.failure.details.pane_id);
38053
- const paneLabel = String(launched.failure.details.pane_label);
38054
- state.pending_pane_id = paneId;
38055
- state.pending_pane_label = paneLabel;
38056
- state.pending_floating_exit = null;
38057
- state.role_panes[role] = {
38058
- pane_id: paneId,
38059
- label: paneLabel,
38060
- profile_fingerprint: profileFingerprint
38061
- };
38062
- yield* store.save(state, root);
38063
- return yield* new HerdrError({
38064
- message: launched.failure.message,
38065
- ...launched.failure.command !== undefined ? { command: launched.failure.command } : {},
38066
- details: {
38067
- ...launched.failure.details ?? {},
38068
- task_attempted: taskRef.task,
38069
- artifact: artifactRel,
38070
- pending_preserved: true
38071
- }
38072
- });
38073
- }
38074
- const rollbackErrors = yield* rollbackLaunch();
38075
- return yield* herdrAfterRollback(launched.failure, {
38076
- task_attempted: taskRef.task,
38077
- artifact: artifactRel
38078
- }, rollbackErrors);
38079
- }
38080
- const r = launched.success;
38081
- launch2 = {
38082
- mode: "interactive",
38083
- pane_id: r.pane_id,
38084
- label: r.label,
38085
- reused: r.reused,
38086
- cmd,
38087
- prompt,
38088
- pane_style: cfg.pane_style,
38089
- pane_style_effective: paneStyle.effective,
38090
- prompt_accepted: r.prompt_accepted,
38091
- prompt_attempts: r.prompt_attempts,
38092
- last_status: r.last_status ?? null
38093
- };
38094
- state.pending_pane_id = r.pane_id;
38095
- state.pending_pane_label = r.label;
38096
- state.pending_floating_exit = null;
38097
- state.role_panes[role] = {
38098
- pane_id: r.pane_id,
38099
- label: r.label,
38100
- profile_fingerprint: profileFingerprint
38101
- };
38213
+ const rollbackErrors = yield* rollbackLaunch();
38214
+ return yield* herdrAfterRollback(launched.failure, {
38215
+ task_attempted: taskRef.task,
38216
+ artifact: artifactRel
38217
+ }, rollbackErrors);
38102
38218
  }
38219
+ const r = launched.success;
38220
+ launch2 = {
38221
+ mode: "interactive",
38222
+ pane_id: r.pane_id,
38223
+ label: r.label,
38224
+ reused: r.reused,
38225
+ cmd,
38226
+ prompt,
38227
+ prompt_accepted: r.prompt_accepted,
38228
+ prompt_attempts: r.prompt_attempts,
38229
+ last_status: r.last_status ?? null
38230
+ };
38231
+ state.pending_pane_id = r.pane_id;
38232
+ state.pending_pane_label = r.label;
38233
+ state.role_panes[role] = {
38234
+ pane_id: r.pane_id,
38235
+ label: r.label,
38236
+ profile_fingerprint: profileFingerprint
38237
+ };
38103
38238
  const launchedAt = yield* exports_Clock.currentTimeMillis;
38104
38239
  markPending(launchedAt);
38105
38240
  yield* store.save(state, root);
@@ -38131,12 +38266,6 @@ function deepMergeProfiles(existing, incoming) {
38131
38266
  }
38132
38267
  return out;
38133
38268
  }
38134
- function preservePaneStyle(prev) {
38135
- const v = prev.pane_style;
38136
- if (v === "regular" || v === "floating")
38137
- return v;
38138
- return;
38139
- }
38140
38269
  function buildProfiles(has4) {
38141
38270
  const profiles = {};
38142
38271
  if (has4.pi) {
@@ -38195,16 +38324,12 @@ function buildGlobalConfig(opts) {
38195
38324
  if (!force && prev.profiles && typeof prev.profiles === "object") {
38196
38325
  nextProfiles = deepMergeProfiles(prev.profiles, profiles);
38197
38326
  }
38198
- const preservedPaneStyle = preservePaneStyle(prev);
38199
38327
  const globalConfig = {
38200
38328
  profiles: nextProfiles,
38201
38329
  roles: force || !prev.roles ? roles : prev.roles,
38202
38330
  review_round_cap: typeof prev.review_round_cap === "number" ? prev.review_round_cap : 3,
38203
38331
  timeouts_ms: prev.timeouts_ms && typeof prev.timeouts_ms === "object" ? prev.timeouts_ms : { ...DEFAULT_TIMEOUTS }
38204
38332
  };
38205
- if (preservedPaneStyle !== undefined) {
38206
- globalConfig.pane_style = preservedPaneStyle;
38207
- }
38208
38333
  return globalConfig;
38209
38334
  }
38210
38335
  function detectionNotes(has4) {
@@ -38253,53 +38378,8 @@ function mergeAgentsMd(existing) {
38253
38378
  `;
38254
38379
  return `${existing}${sep2}${AGENTS_SECTION}`;
38255
38380
  }
38256
- var provisionHerdrPlugin = (opts) => exports_Effect.gen(function* () {
38257
- const fs4 = yield* FileSystem;
38258
- const herdr = yield* Herdr;
38259
- const notes = [];
38260
- const srcExists = yield* fs4.exists(opts.srcDir);
38261
- if (!srcExists) {
38262
- notes.push("herdr-plugin missing from package \u2014 reinstall @naxodev/apnea");
38263
- return { copied: null, linked: false, already_linked: false, notes };
38264
- }
38265
- yield* fs4.copyDir(opts.srcDir, opts.destDir);
38266
- const runTask = path8.join(opts.destDir, "scripts", "run-task.sh");
38267
- if (yield* fs4.exists(runTask)) {
38268
- yield* fs4.chmod(runTask, 493);
38269
- }
38270
- if (!supportsFloating(opts.version)) {
38271
- const ver = opts.version == null ? "unknown" : `${opts.version[0]}.${opts.version[1]}.${opts.version[2]}`;
38272
- notes.push(`herdr ${ver} < 0.7.4 \u2014 floating panes unavailable; run \`herdr update\`, then re-run /apnea setup`);
38273
- return {
38274
- copied: opts.destDir,
38275
- linked: false,
38276
- already_linked: false,
38277
- notes
38278
- };
38279
- }
38280
- if (yield* herdr.hasApneaPlugin) {
38281
- return {
38282
- copied: opts.destDir,
38283
- linked: false,
38284
- already_linked: true,
38285
- notes
38286
- };
38287
- }
38288
- const linkResult = yield* herdr.linkPlugin(opts.destDir);
38289
- if (!linkResult.ok) {
38290
- notes.push(`herdr plugin link failed: ${linkResult.raw.trim() || "(no output)"}`);
38291
- return {
38292
- copied: opts.destDir,
38293
- linked: false,
38294
- already_linked: false,
38295
- notes
38296
- };
38297
- }
38298
- return { copied: opts.destDir, linked: true, already_linked: false, notes };
38299
- });
38300
38381
  var setupWorkflow = (params, root, deps) => exports_Effect.gen(function* () {
38301
38382
  const fs4 = yield* FileSystem;
38302
- const herdr = yield* Herdr;
38303
38383
  const readJsonSafe = (filePath) => exports_Effect.gen(function* () {
38304
38384
  const present = yield* fs4.exists(filePath);
38305
38385
  if (!present)
@@ -38355,18 +38435,6 @@ var setupWorkflow = (params, root, deps) => exports_Effect.gen(function* () {
38355
38435
  const e = materialized.failure;
38356
38436
  missing.push(`role agent dir failed: ${e instanceof Error ? e.message : String(e)}`);
38357
38437
  }
38358
- let herdrPlugin = null;
38359
- let herdrVer = null;
38360
- if (has4.herdr) {
38361
- const version2 = yield* herdr.version;
38362
- herdrVer = version2 == null ? null : `${version2[0]}.${version2[1]}.${version2[2]}`;
38363
- herdrPlugin = yield* provisionHerdrPlugin({
38364
- srcDir: path8.join(packageRoot(), "herdr-plugin"),
38365
- destDir: path8.join(path8.dirname(globalConfigPath()), "herdr-plugin"),
38366
- version: version2
38367
- });
38368
- missing.push(...herdrPlugin.notes);
38369
- }
38370
38438
  let agentsMdPath = null;
38371
38439
  if (params.agents_md) {
38372
38440
  const target2 = path8.join(root, "AGENTS.md");
@@ -38391,14 +38459,6 @@ var setupWorkflow = (params, root, deps) => exports_Effect.gen(function* () {
38391
38459
  if (params.agents_md) {
38392
38460
  data.agents_md = agentsMdPath;
38393
38461
  }
38394
- if (has4.herdr) {
38395
- data.herdr_version = herdrVer;
38396
- data.herdr_plugin = herdrPlugin ? {
38397
- copied: herdrPlugin.copied,
38398
- linked: herdrPlugin.linked,
38399
- already_linked: herdrPlugin.already_linked
38400
- } : null;
38401
- }
38402
38462
  return ok(`wrote global config ${gPath}`, data);
38403
38463
  });
38404
38464
 
@@ -38487,7 +38547,6 @@ var startWorkflow = (params, root) => exports_Effect.gen(function* () {
38487
38547
  pending_role: null,
38488
38548
  pending_pane_id: null,
38489
38549
  pending_pane_label: null,
38490
- pending_floating_exit: null,
38491
38550
  pending_started_at: null,
38492
38551
  pending_deadline_ms: null,
38493
38552
  pending_nudged_at: null,
@@ -38682,13 +38741,6 @@ var waitWorkflow = (params, root, hooks = {}) => exports_Effect.gen(function* ()
38682
38741
  const text = yield* fs4.readFile(artifactAbs);
38683
38742
  return parseFrontMatter(text);
38684
38743
  });
38685
- const readFloatingExitCode = (exitAbs) => exports_Effect.gen(function* () {
38686
- const present = yield* fs4.exists(exitAbs);
38687
- if (!present)
38688
- return null;
38689
- const text = yield* fs4.readFile(exitAbs);
38690
- return parseFloatingExit(text);
38691
- });
38692
38744
  const advanceOnComplete = (fm, msg = "artifact ready") => exports_Effect.gen(function* () {
38693
38745
  if (fm.rework !== undefined && (kind !== "code_review" || fm.verdict !== "CHANGES_REQUIRED")) {
38694
38746
  return yield* new ArtifactInvalid({
@@ -38742,7 +38794,6 @@ var waitWorkflow = (params, root, hooks = {}) => exports_Effect.gen(function* ()
38742
38794
  state.pending_role = null;
38743
38795
  state.pending_pane_id = null;
38744
38796
  state.pending_pane_label = null;
38745
- state.pending_floating_exit = null;
38746
38797
  state.pending_started_at = null;
38747
38798
  state.pending_deadline_ms = null;
38748
38799
  resetRecoveryLadder(state);
@@ -38758,11 +38809,9 @@ var waitWorkflow = (params, root, hooks = {}) => exports_Effect.gen(function* ()
38758
38809
  step: next
38759
38810
  }, nextAfter(next));
38760
38811
  });
38761
- const floatingFlushMs = 2000;
38762
38812
  let lastStatus = "waiting";
38763
38813
  let shellOnlyPolls = 0;
38764
38814
  let idleSince = null;
38765
- let floatingExitSeenAt = null;
38766
38815
  let nudged = state.pending_nudged_at != null;
38767
38816
  let extendedOnce = state.pending_extended;
38768
38817
  let finalNudgeGrace = state.pending_final_grace;
@@ -38805,33 +38854,6 @@ var waitWorkflow = (params, root, hooks = {}) => exports_Effect.gen(function* ()
38805
38854
  if (isCompleteArtifact(fm, { requireVerdict })) {
38806
38855
  return yield* advanceOnComplete(fm, nudged ? "artifact ready after nudge" : "artifact ready");
38807
38856
  }
38808
- if (state.pending_floating_exit) {
38809
- const exitAbs = abs2(state.pending_floating_exit, root);
38810
- const code = yield* readFloatingExitCode(exitAbs);
38811
- if (code != null) {
38812
- lastStatus = `floating_exit_${code}`;
38813
- floatingExitSeenAt ??= now2;
38814
- if (now2 - floatingExitSeenAt >= floatingFlushMs) {
38815
- const again = yield* readArtifact();
38816
- if (isCompleteArtifact(again, { requireVerdict })) {
38817
- return yield* advanceOnComplete(again, "artifact ready (floating oneshot exited)");
38818
- }
38819
- state.pending_floating_exit = null;
38820
- state.last_error = `floating oneshot exited ${code} without ${pendingArtifact}`;
38821
- yield* store.save(state, root);
38822
- return yield* new HerdrError({
38823
- message: `floating ${state.pending_role} exited (code ${code}) without writing ${pendingArtifact}`,
38824
- details: {
38825
- exit_code: code,
38826
- last_agent_status: lastStatus,
38827
- hint: code === 129 ? "popup received Hangup (dismiss/focus steal) \u2014 re-dispatch same round; keep focus on the popup" : "inspect oneshot output; re-dispatch same round or set pane_style=regular"
38828
- }
38829
- });
38830
- }
38831
- } else {
38832
- lastStatus = "floating_running";
38833
- }
38834
- }
38835
38857
  if ((yield* herdr.enabled) && state.pending_pane_id) {
38836
38858
  const info = yield* herdr.paneGet(state.pending_pane_id);
38837
38859
  if (!info.ok) {