@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.
@@ -1,8 +1,6 @@
1
1
  import * as path from "node:path"
2
2
  import { Cause, Clock, Effect, Exit, Result } from "effect"
3
- import { effectivePaneStyle, supportsFloating } from "../domain/herdr.ts"
4
3
  import {
5
- abs,
6
4
  packageRoot,
7
5
  phaseDir,
8
6
  planPath,
@@ -110,8 +108,8 @@ function herdrAfterRollback(
110
108
  }
111
109
 
112
110
  /**
113
- * Write task file, open interactive harness TUI in a Herdr pane (or a
114
- * floating oneshot popup), wait until idle, submit a short pointer prompt.
111
+ * Write task file, open an interactive harness TUI in a reusable Herdr pane,
112
+ * wait until idle, then submit a short pointer prompt.
115
113
  * Refusals are tagged failures only — never ok:false ToolResults.
116
114
  */
117
115
  export const dispatchWorkflow = (
@@ -345,57 +343,13 @@ export const dispatchWorkflow = (
345
343
  })
346
344
  }
347
345
 
348
- const paneStyle = effectivePaneStyle(cfg.pane_style, role)
349
- let roleCmd: string[] | null = null
350
- let profileFingerprint: string | null = null
351
-
352
- // Everything that can be checked without a task path runs before any
353
- // artifact mutation. Only the pane/script launch race remains afterward.
354
- if (herdrAvailability !== "unavailable") {
355
- if (paneStyle.style === "floating") {
356
- const version = yield* herdr.version
357
- if (!supportsFloating(version)) {
358
- return yield* new HerdrError({
359
- message:
360
- "floating panes need herdr >= 0.7.4 — run `herdr update`, or set pane_style=regular",
361
- })
362
- }
363
- if (!(yield* herdr.hasApneaPlugin)) {
364
- return yield* new HerdrError({
365
- message: `apnea herdr plugin not linked. Run /apnea setup, or: herdr plugin link ${livePackageRoot}/herdr-plugin`,
366
- })
367
- }
368
- if (state.pending_floating_exit) {
369
- const prevExitAbs = abs(state.pending_floating_exit, root)
370
- if (!(yield* fs.exists(prevExitAbs))) {
371
- return yield* new GateRefused({
372
- gate: "floating_in_flight",
373
- message:
374
- "floating oneshot already in flight (popup still open). Call workflow_wait, or dismiss the popup and re-dispatch after it exits",
375
- details: {
376
- pending_artifact: state.pending_artifact,
377
- pending_floating_exit: state.pending_floating_exit,
378
- },
379
- })
380
- }
381
- }
382
- const cmdResult = yield* Effect.result(
383
- config.resolveRoleCmd(cfg, role, "oneshot"),
384
- )
385
- if (Result.isFailure(cmdResult)) {
386
- return yield* new HerdrError({
387
- message: `floating dispatch requires cmd_oneshot on the role profile: ${cmdResult.failure.message}`,
388
- })
389
- }
390
- roleCmd = cmdResult.success
391
- } else {
392
- roleCmd = yield* config.resolveRoleCmd(cfg, role, "interactive")
393
- profileFingerprint = JSON.stringify([
394
- cfg.roles[role]?.profile ?? null,
395
- roleCmd,
396
- ])
397
- }
398
- }
346
+ // Resolve the interactive command before mutating artifacts. Apnea never
347
+ // falls back to a profile's oneshot command, even outside Herdr.
348
+ const roleCmd = yield* config.resolveRoleCmd(cfg, role, "interactive")
349
+ const profileFingerprint = JSON.stringify([
350
+ cfg.roles[role]?.profile ?? null,
351
+ roleCmd,
352
+ ])
399
353
 
400
354
  if (role === "reviewer") {
401
355
  state.reviewer_tree_fingerprint = yield* vcsSvc.treeFingerprint(
@@ -451,8 +405,6 @@ export const dispatchWorkflow = (
451
405
 
452
406
  let launch: Record<string, unknown> = {
453
407
  mode: ROLE_MODE[role],
454
- pane_style: cfg.pane_style,
455
- pane_style_effective: paneStyle.effective,
456
408
  }
457
409
 
458
410
  const rollbackLaunch = (restoreState = true) =>
@@ -466,14 +418,6 @@ export const dispatchWorkflow = (
466
418
  }
467
419
  })
468
420
  yield* attempt("remove task", fs.remove(taskFile))
469
- yield* attempt(
470
- "remove floating script",
471
- fs.remove(taskFile.replace(/\.md$/, ".sh")),
472
- )
473
- yield* attempt(
474
- "remove floating exit marker",
475
- fs.remove(taskFile.replace(/\.md$/, ".exit")),
476
- )
477
421
  yield* attempt("remove replacement artifact", fs.remove(artifactAbs))
478
422
  if (backupAbs != null) {
479
423
  yield* attempt(
@@ -505,7 +449,6 @@ export const dispatchWorkflow = (
505
449
  markPending(preparedAt)
506
450
  state.pending_pane_id = null
507
451
  state.pending_pane_label = null
508
- state.pending_floating_exit = null
509
452
  const preparedState = yield* Effect.exit(store.save(state, root))
510
453
  if (Exit.isFailure(preparedState)) {
511
454
  const persistenceError = new HerdrError({
@@ -544,127 +487,67 @@ export const dispatchWorkflow = (
544
487
  )
545
488
  }
546
489
 
547
- if (paneStyle.style === "floating") {
548
- const cmd = roleCmd!
549
- const scriptAbs = taskFile.replace(/\.md$/, ".sh")
550
- const exitAbs = taskFile.replace(/\.md$/, ".exit")
551
- // Drop stale exit marker so wait cannot see a previous run's code.
552
- yield* fs.remove(exitAbs)
553
- const scriptWritten = yield* Effect.result(
554
- herdr.writeFloatingTaskScript(scriptAbs, root, cmd, prompt, exitAbs),
555
- )
556
- if (Result.isFailure(scriptWritten)) {
557
- const rollbackErrors = yield* rollbackLaunch()
558
- return yield* herdrAfterRollback(
559
- scriptWritten.failure,
560
- {
561
- task_attempted: taskRef.task,
562
- artifact: artifactRel,
563
- },
564
- rollbackErrors,
565
- )
566
- }
567
- // Popups have no pane id — liveness is the exit file; leave role_panes alone.
568
- state.pending_pane_id = null
569
- state.pending_pane_label = null
570
- state.pending_floating_exit = rel(exitAbs, root)
571
- yield* store.save(state, root)
572
- const opened = yield* Effect.result(
573
- herdr.openFloatingPane(scriptAbs, root),
574
- )
575
- if (Result.isFailure(opened)) {
490
+ // Interactive TUI: open harness, wait idle, submit pointer via pane run.
491
+ const cmd = roleCmd
492
+ const remembered = state.role_panes[role] ?? null
493
+ const prefer =
494
+ remembered?.profile_fingerprint === profileFingerprint ? remembered : null
495
+ const launched = yield* Effect.result(
496
+ herdr.runInteractivePrompt(role, cmd, prompt, prefer),
497
+ )
498
+ if (Result.isFailure(launched)) {
499
+ if (launched.failure.details?.delivery === "unknown") {
500
+ const paneId = String(launched.failure.details.pane_id)
501
+ const paneLabel = String(launched.failure.details.pane_label)
502
+ state.pending_pane_id = paneId
503
+ state.pending_pane_label = paneLabel
504
+ state.role_panes[role] = {
505
+ pane_id: paneId,
506
+ label: paneLabel,
507
+ profile_fingerprint: profileFingerprint,
508
+ }
509
+ yield* store.save(state, root)
576
510
  return yield* new HerdrError({
577
- message: opened.failure.message,
578
- ...(opened.failure.command !== undefined
579
- ? { command: opened.failure.command }
511
+ message: launched.failure.message,
512
+ ...(launched.failure.command !== undefined
513
+ ? { command: launched.failure.command }
580
514
  : {}),
581
515
  details: {
582
- ...(opened.failure.details ?? {}),
583
- delivery: "unknown",
516
+ ...(launched.failure.details ?? {}),
584
517
  task_attempted: taskRef.task,
585
518
  artifact: artifactRel,
586
519
  pending_preserved: true,
587
520
  },
588
521
  })
589
522
  }
590
- launch = {
591
- mode: "oneshot",
592
- pane_style: cfg.pane_style,
593
- pane_style_effective: "floating",
594
- script: rel(scriptAbs, root),
595
- exit: rel(exitAbs, root),
596
- cmd,
597
- prompt,
598
- }
599
- } else {
600
- // Interactive TUI: open harness, wait idle, submit pointer via pane run.
601
- const cmd = roleCmd!
602
- const remembered = state.role_panes[role] ?? null
603
- const prefer =
604
- remembered?.profile_fingerprint === profileFingerprint
605
- ? remembered
606
- : null
607
- const launched = yield* Effect.result(
608
- herdr.runInteractivePrompt(role, cmd, prompt, prefer),
523
+ const rollbackErrors = yield* rollbackLaunch()
524
+ return yield* herdrAfterRollback(
525
+ launched.failure,
526
+ {
527
+ task_attempted: taskRef.task,
528
+ artifact: artifactRel,
529
+ },
530
+ rollbackErrors,
609
531
  )
610
- if (Result.isFailure(launched)) {
611
- if (launched.failure.details?.delivery === "unknown") {
612
- const paneId = String(launched.failure.details.pane_id)
613
- const paneLabel = String(launched.failure.details.pane_label)
614
- state.pending_pane_id = paneId
615
- state.pending_pane_label = paneLabel
616
- state.pending_floating_exit = null
617
- state.role_panes[role] = {
618
- pane_id: paneId,
619
- label: paneLabel,
620
- profile_fingerprint: profileFingerprint,
621
- }
622
- yield* store.save(state, root)
623
- return yield* new HerdrError({
624
- message: launched.failure.message,
625
- ...(launched.failure.command !== undefined
626
- ? { command: launched.failure.command }
627
- : {}),
628
- details: {
629
- ...(launched.failure.details ?? {}),
630
- task_attempted: taskRef.task,
631
- artifact: artifactRel,
632
- pending_preserved: true,
633
- },
634
- })
635
- }
636
- const rollbackErrors = yield* rollbackLaunch()
637
- return yield* herdrAfterRollback(
638
- launched.failure,
639
- {
640
- task_attempted: taskRef.task,
641
- artifact: artifactRel,
642
- },
643
- rollbackErrors,
644
- )
645
- }
646
- const r = launched.success
647
- launch = {
648
- mode: "interactive",
649
- pane_id: r.pane_id,
650
- label: r.label,
651
- reused: r.reused,
652
- cmd,
653
- prompt,
654
- pane_style: cfg.pane_style,
655
- pane_style_effective: paneStyle.effective,
656
- prompt_accepted: r.prompt_accepted,
657
- prompt_attempts: r.prompt_attempts,
658
- last_status: r.last_status ?? null,
659
- }
660
- state.pending_pane_id = r.pane_id
661
- state.pending_pane_label = r.label
662
- state.pending_floating_exit = null
663
- state.role_panes[role] = {
664
- pane_id: r.pane_id,
665
- label: r.label,
666
- profile_fingerprint: profileFingerprint,
667
- }
532
+ }
533
+ const r = launched.success
534
+ launch = {
535
+ mode: "interactive",
536
+ pane_id: r.pane_id,
537
+ label: r.label,
538
+ reused: r.reused,
539
+ cmd,
540
+ prompt,
541
+ prompt_accepted: r.prompt_accepted,
542
+ prompt_attempts: r.prompt_attempts,
543
+ last_status: r.last_status ?? null,
544
+ }
545
+ state.pending_pane_id = r.pane_id
546
+ state.pending_pane_label = r.label
547
+ state.role_panes[role] = {
548
+ pane_id: r.pane_id,
549
+ label: r.label,
550
+ profile_fingerprint: profileFingerprint,
668
551
  }
669
552
 
670
553
  // After the launch, not before it: `runInteractivePrompt` blocks in
@@ -1,11 +1,6 @@
1
1
  import * as path from "node:path"
2
2
  import { Effect, Result } from "effect"
3
- import { supportsFloating } from "../domain/herdr.ts"
4
- import {
5
- globalConfigPath,
6
- packageRoot,
7
- projectConfigPath,
8
- } from "../domain/paths.ts"
3
+ import { globalConfigPath, projectConfigPath } from "../domain/paths.ts"
9
4
  import {
10
5
  buildGlobalConfig,
11
6
  detectionNotes,
@@ -16,7 +11,6 @@ import { ConfigError, type AppError } from "../errors.ts"
16
11
  import { ok, type ToolResult } from "../result.ts"
17
12
  import { decodeGlobalConfig } from "../schema/config.ts"
18
13
  import { FileSystem } from "../services/file-system.ts"
19
- import { Herdr } from "../services/herdr.ts"
20
14
 
21
15
  export type SetupParams = {
22
16
  /** Write .apnea/config.json role bindings in cwd */
@@ -78,81 +72,6 @@ export type SetupDeps = {
78
72
  materializeRoleAgentDir: () => string | null
79
73
  }
80
74
 
81
- export type ProvisionResult = {
82
- copied: string | null // dest dir, or null when skipped
83
- linked: boolean // true only when we ran `herdr plugin link` OK
84
- already_linked: boolean
85
- notes: string[]
86
- }
87
-
88
- /**
89
- * Copy the package's herdr-plugin into a stable config-local path and, when
90
- * herdr is new enough and the plugin isn't already linked, run
91
- * `herdr plugin link`. Never fails — every branch is a note.
92
- */
93
- export const provisionHerdrPlugin = (opts: {
94
- srcDir: string // packageRoot()/herdr-plugin
95
- destDir: string // dirname(globalConfigPath())/herdr-plugin
96
- version: [number, number, number] | null // herdr.version
97
- }): Effect.Effect<ProvisionResult, never, FileSystem | Herdr> =>
98
- Effect.gen(function* () {
99
- const fs = yield* FileSystem
100
- const herdr = yield* Herdr
101
- const notes: string[] = []
102
-
103
- const srcExists = yield* fs.exists(opts.srcDir)
104
- if (!srcExists) {
105
- notes.push("herdr-plugin missing from package — reinstall @naxodev/apnea")
106
- return { copied: null, linked: false, already_linked: false, notes }
107
- }
108
-
109
- yield* fs.copyDir(opts.srcDir, opts.destDir)
110
- const runTask = path.join(opts.destDir, "scripts", "run-task.sh")
111
- if (yield* fs.exists(runTask)) {
112
- yield* fs.chmod(runTask, 0o755)
113
- }
114
-
115
- if (!supportsFloating(opts.version)) {
116
- const ver =
117
- opts.version == null
118
- ? "unknown"
119
- : `${opts.version[0]}.${opts.version[1]}.${opts.version[2]}`
120
- notes.push(
121
- `herdr ${ver} < 0.7.4 — floating panes unavailable; run \`herdr update\`, then re-run /apnea setup`,
122
- )
123
- return {
124
- copied: opts.destDir,
125
- linked: false,
126
- already_linked: false,
127
- notes,
128
- }
129
- }
130
-
131
- if (yield* herdr.hasApneaPlugin) {
132
- return {
133
- copied: opts.destDir,
134
- linked: false,
135
- already_linked: true,
136
- notes,
137
- }
138
- }
139
-
140
- const linkResult = yield* herdr.linkPlugin(opts.destDir)
141
- if (!linkResult.ok) {
142
- notes.push(
143
- `herdr plugin link failed: ${linkResult.raw.trim() || "(no output)"}`,
144
- )
145
- return {
146
- copied: opts.destDir,
147
- linked: false,
148
- already_linked: false,
149
- notes,
150
- }
151
- }
152
-
153
- return { copied: opts.destDir, linked: true, already_linked: false, notes }
154
- })
155
-
156
75
  /**
157
76
  * Deterministic Apnea setup: detect binaries, write global profiles
158
77
  * (and optional project role bindings). Never writes cmd into project config.
@@ -161,10 +80,9 @@ export const setupWorkflow = (
161
80
  params: SetupParams,
162
81
  root: string,
163
82
  deps: SetupDeps,
164
- ): Effect.Effect<ToolResult, AppError, FileSystem | Herdr> =>
83
+ ): Effect.Effect<ToolResult, AppError, FileSystem> =>
165
84
  Effect.gen(function* () {
166
85
  const fs = yield* FileSystem
167
- const herdr = yield* Herdr
168
86
 
169
87
  const readJsonSafe = (
170
88
  filePath: string,
@@ -241,20 +159,6 @@ export const setupWorkflow = (
241
159
  )
242
160
  }
243
161
 
244
- let herdrPlugin: ProvisionResult | null = null
245
- let herdrVer: string | null = null
246
- if (has.herdr) {
247
- const version = yield* herdr.version
248
- herdrVer =
249
- version == null ? null : `${version[0]}.${version[1]}.${version[2]}`
250
- herdrPlugin = yield* provisionHerdrPlugin({
251
- srcDir: path.join(packageRoot(), "herdr-plugin"),
252
- destDir: path.join(path.dirname(globalConfigPath()), "herdr-plugin"),
253
- version,
254
- })
255
- missing.push(...herdrPlugin.notes)
256
- }
257
-
258
162
  let agentsMdPath: string | null = null
259
163
  if (params.agents_md) {
260
164
  const target = path.join(root, "AGENTS.md")
@@ -286,16 +190,5 @@ export const setupWorkflow = (
286
190
  if (params.agents_md) {
287
191
  data.agents_md = agentsMdPath
288
192
  }
289
- if (has.herdr) {
290
- data.herdr_version = herdrVer
291
- data.herdr_plugin = herdrPlugin
292
- ? {
293
- copied: herdrPlugin.copied,
294
- linked: herdrPlugin.linked,
295
- already_linked: herdrPlugin.already_linked,
296
- }
297
- : null
298
- }
299
-
300
193
  return ok(`wrote global config ${gPath}`, data)
301
194
  })
@@ -114,7 +114,6 @@ export const startWorkflow = (
114
114
  pending_role: null,
115
115
  pending_pane_id: null,
116
116
  pending_pane_label: null,
117
- pending_floating_exit: null,
118
117
  pending_started_at: null,
119
118
  pending_deadline_ms: null,
120
119
  pending_nudged_at: null,
@@ -7,11 +7,7 @@ import {
7
7
  isCompleteArtifact,
8
8
  parseFrontMatter,
9
9
  } from "../domain/frontmatter.ts"
10
- import {
11
- looksLikeShellOnly,
12
- parseFloatingExit,
13
- shellJoin,
14
- } from "../domain/herdr.ts"
10
+ import { looksLikeShellOnly, shellJoin } from "../domain/herdr.ts"
15
11
  import { abs, rel } from "../domain/paths.ts"
16
12
  import {
17
13
  nextAfter,
@@ -290,16 +286,6 @@ export const waitWorkflow = (
290
286
  return parseFrontMatter(text)
291
287
  })
292
288
 
293
- const readFloatingExitCode = (
294
- exitAbs: string,
295
- ): Effect.Effect<number | null> =>
296
- Effect.gen(function* () {
297
- const present = yield* fs.exists(exitAbs)
298
- if (!present) return null
299
- const text = yield* fs.readFile(exitAbs)
300
- return parseFloatingExit(text)
301
- })
302
-
303
289
  const advanceOnComplete = (
304
290
  fm: FrontMatter,
305
291
  msg = "artifact ready",
@@ -384,7 +370,6 @@ export const waitWorkflow = (
384
370
  state.pending_role = null
385
371
  state.pending_pane_id = null
386
372
  state.pending_pane_label = null
387
- state.pending_floating_exit = null
388
373
  state.pending_started_at = null
389
374
  state.pending_deadline_ms = null
390
375
  resetRecoveryLadder(state)
@@ -406,8 +391,6 @@ export const waitWorkflow = (
406
391
  )
407
392
  })
408
393
 
409
- const floatingFlushMs = 2_000
410
-
411
394
  let lastStatus = "waiting"
412
395
  /**
413
396
  * Both counters below are per-call on purpose. They measure a duration
@@ -418,7 +401,6 @@ export const waitWorkflow = (
418
401
  */
419
402
  let shellOnlyPolls = 0
420
403
  let idleSince: number | null = null
421
- let floatingExitSeenAt: number | null = null
422
404
  let nudged = state.pending_nudged_at != null
423
405
  let extendedOnce = state.pending_extended
424
406
  let finalNudgeGrace = state.pending_final_grace
@@ -497,44 +479,6 @@ export const waitWorkflow = (
497
479
  )
498
480
  }
499
481
 
500
- // Floating oneshot: no pane id — exit file is liveness. Fail closed
501
- // when the popup dies without a complete artifact instead of
502
- // hanging until timeout.
503
- if (state.pending_floating_exit) {
504
- const exitAbs = abs(state.pending_floating_exit, root)
505
- const code = yield* readFloatingExitCode(exitAbs)
506
- if (code != null) {
507
- lastStatus = `floating_exit_${code}`
508
- floatingExitSeenAt ??= now
509
- // Short flush window: oneshot may finish writing as the process exits.
510
- if (now - floatingExitSeenAt >= floatingFlushMs) {
511
- const again = yield* readArtifact()
512
- if (isCompleteArtifact(again, { requireVerdict })) {
513
- return yield* advanceOnComplete(
514
- again!,
515
- "artifact ready (floating oneshot exited)",
516
- )
517
- }
518
- state.pending_floating_exit = null
519
- state.last_error = `floating oneshot exited ${code} without ${pendingArtifact}`
520
- yield* store.save(state, root)
521
- return yield* new HerdrError({
522
- message: `floating ${state.pending_role} exited (code ${code}) without writing ${pendingArtifact}`,
523
- details: {
524
- exit_code: code,
525
- last_agent_status: lastStatus,
526
- hint:
527
- code === 129
528
- ? "popup received Hangup (dismiss/focus steal) — re-dispatch same round; keep focus on the popup"
529
- : "inspect oneshot output; re-dispatch same round or set pane_style=regular",
530
- },
531
- })
532
- }
533
- } else {
534
- lastStatus = "floating_running"
535
- }
536
- }
537
-
538
482
  if ((yield* herdr.enabled) && state.pending_pane_id) {
539
483
  const info = yield* herdr.paneGet(state.pending_pane_id)
540
484
  if (!info.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naxodev/apnea",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Host-neutral multi-role workflow engine and standalone Bun CLI",
5
5
  "license": "MIT",
6
6
  "author": "Nacho Vazquez",
@@ -39,7 +39,6 @@
39
39
  "docs/adr",
40
40
  "docs/protocol",
41
41
  "extension",
42
- "herdr-plugin",
43
42
  "schemas",
44
43
  "!extension/**/*.test.ts",
45
44
  "!extension/test"
@@ -35,16 +35,16 @@
35
35
  }
36
36
  }
37
37
  },
38
+ "pane_style": {
39
+ "type": "string",
40
+ "enum": ["regular", "floating"],
41
+ "deprecated": true,
42
+ "description": "Deprecated migration-only setting. The runtime accepts and ignores it during migration."
43
+ },
38
44
  "review_round_cap": { "type": "integer", "minimum": 1, "maximum": 20 },
39
45
  "timeouts_ms": {
40
46
  "type": "object",
41
47
  "additionalProperties": { "type": "integer", "minimum": 1000 }
42
- },
43
- "pane_style": {
44
- "type": "string",
45
- "enum": ["regular", "floating"],
46
- "default": "regular",
47
- "description": "Herdr pane style for role dispatches. \"floating\" applies to oneshot roles only and requires herdr >= 0.7.4; interactive roles always use regular panes. Default: regular."
48
48
  }
49
49
  }
50
50
  }
@@ -34,9 +34,13 @@
34
34
  "last_error": { "type": ["string", "null"] },
35
35
  "pending_artifact": { "type": ["string", "null"] },
36
36
  "pending_role": { "type": ["string", "null"] },
37
+ "pending_floating_exit": {
38
+ "type": "null",
39
+ "deprecated": true,
40
+ "description": "Deprecated migration-only field. Missing or null values migrate; active non-null legacy floating runs must be abandoned."
41
+ },
37
42
  "pending_pane_id": { "type": ["string", "null"] },
38
43
  "pending_pane_label": { "type": ["string", "null"] },
39
- "pending_floating_exit": { "type": ["string", "null"] },
40
44
  "pending_started_at": { "type": ["integer", "null"] },
41
45
  "pending_deadline_ms": { "type": ["integer", "null"] },
42
46
  "pending_nudged_at": { "type": ["integer", "null"] },
@@ -1,15 +0,0 @@
1
- id = "apnea"
2
- name = "Apnea"
3
- version = "0.1.0"
4
- description = "Apnea floating-pane worker for oneshot role dispatches."
5
- min_herdr_version = "0.7.4"
6
- platforms = ["linux", "macos"]
7
-
8
- [[panes]]
9
- id = "worker"
10
- title = "Apnea worker"
11
- placement = "overlay"
12
- # Absolute bash + plugin-relative script. Bare `bash` fails on stripped plugin
13
- # PATH (exit 127). Do not pass a foreign --cwd to plugin pane open — relative
14
- # script paths resolve against that cwd, not plugin_root.
15
- command = ["/bin/bash", "scripts/run-task.sh"]
@@ -1,8 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
- if [[ -z "${APNEA_TASK_SCRIPT:-}" ]]; then
4
- echo "apnea: APNEA_TASK_SCRIPT not set (open this pane via apnea dispatch)" >&2
5
- exit 64
6
- fi
7
- # Absolute bash: popup PATH may not resolve bare `bash` (exit 127).
8
- exec /bin/bash "$APNEA_TASK_SCRIPT"