@naxodev/apnea 0.2.0 → 0.2.2
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/README.md +18 -1
- package/SECURITY.md +36 -0
- package/briefs/orchestrator.md +4 -3
- package/dist/cli.js +8375 -15093
- package/docs/protocol/artifacts.md +18 -2
- package/docs/protocol/config.md +15 -3
- package/docs/protocol/manual-gate.md +8 -8
- package/docs/protocol/overview.md +17 -4
- package/extension/adapters/commit.ts +5 -1
- package/extension/adapters/dispatch.ts +9 -1
- package/extension/adapters/setup.ts +15 -1
- package/extension/adapters/start.ts +5 -1
- package/extension/adapters/status.ts +17 -2
- package/extension/adapters/wait.ts +6 -1
- package/extension/api.ts +7 -1
- package/extension/cli/main.ts +67 -7
- package/extension/cli/parse.ts +172 -5
- package/extension/domain/paths.ts +2 -11
- package/extension/domain/timeouts.ts +4 -0
- package/extension/domain/types.ts +65 -3
- package/extension/errors.ts +51 -16
- package/extension/operation-hooks.ts +6 -0
- package/extension/registry.ts +29 -15
- package/extension/run-tool.ts +19 -2
- package/extension/schema/config.ts +58 -16
- package/extension/schema/frontmatter.ts +57 -0
- package/extension/schema/state.ts +210 -13
- package/extension/services/app-live.ts +2 -1
- package/extension/services/config.ts +6 -4
- package/extension/services/file-system.ts +346 -75
- package/extension/services/herdr.ts +466 -260
- package/extension/services/operation-lock.ts +452 -0
- package/extension/services/process.ts +477 -0
- package/extension/services/run-store.ts +38 -16
- package/extension/services/vcs.ts +1258 -328
- package/extension/workflows/commit.ts +214 -13
- package/extension/workflows/dispatch.ts +305 -67
- package/extension/workflows/setup.ts +59 -32
- package/extension/workflows/start.ts +6 -4
- package/extension/workflows/status.ts +2 -2
- package/extension/workflows/wait.ts +62 -77
- package/package.json +2 -2
- package/schemas/config.schema.json +5 -1
- package/schemas/state.schema.json +165 -11
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process"
|
|
2
1
|
import * as fs from "node:fs"
|
|
3
2
|
import * as path from "node:path"
|
|
4
3
|
import { Clock, Context, Effect, Layer, Option, Result } from "effect"
|
|
@@ -6,14 +5,28 @@ import { shellJoin } from "../domain/herdr.ts"
|
|
|
6
5
|
import { HerdrError } from "../errors.ts"
|
|
7
6
|
import type { ApneaHostAdapter } from "../host-adapter.ts"
|
|
8
7
|
import { neutralHostAdapter } from "../host-adapter.ts"
|
|
8
|
+
import {
|
|
9
|
+
Process,
|
|
10
|
+
ProcessCancelledError,
|
|
11
|
+
ProcessExitError,
|
|
12
|
+
ProcessTimeoutError,
|
|
13
|
+
type ProcessError,
|
|
14
|
+
type ProcessService,
|
|
15
|
+
} from "./process.ts"
|
|
9
16
|
|
|
10
17
|
export type PaneInfo = {
|
|
11
18
|
ok: boolean
|
|
19
|
+
/** True only when Herdr explicitly reports that this pane does not exist. */
|
|
20
|
+
missing?: boolean
|
|
12
21
|
agent_status?: string
|
|
13
22
|
label?: string
|
|
14
23
|
agent?: string
|
|
15
24
|
}
|
|
16
25
|
export type RolePaneRef = { pane_id: string; label: string }
|
|
26
|
+
/** Persist pane ownership before any task prompt can reach the pane. */
|
|
27
|
+
export type BeforeInteractiveDelivery = (
|
|
28
|
+
pane: RolePaneRef,
|
|
29
|
+
) => Effect.Effect<void, HerdrError>
|
|
17
30
|
export type HerdrAvailability = "available" | "unavailable"
|
|
18
31
|
export type InteractiveLaunch = {
|
|
19
32
|
pane_id: string
|
|
@@ -28,7 +41,7 @@ export interface HerdrService {
|
|
|
28
41
|
readonly enabled: Effect.Effect<boolean>
|
|
29
42
|
/** Dispatch preflight that distinguishes a stale pane from CLI failures. */
|
|
30
43
|
readonly availability: Effect.Effect<HerdrAvailability, HerdrError>
|
|
31
|
-
readonly paneGet: (paneId: string) => Effect.Effect<PaneInfo>
|
|
44
|
+
readonly paneGet: (paneId: string) => Effect.Effect<PaneInfo, HerdrError>
|
|
32
45
|
readonly paneRun: (
|
|
33
46
|
paneId: string,
|
|
34
47
|
command: string,
|
|
@@ -42,6 +55,8 @@ export interface HerdrService {
|
|
|
42
55
|
interactiveCmd: string[],
|
|
43
56
|
prompt: string,
|
|
44
57
|
prefer: RolePaneRef | null,
|
|
58
|
+
beforeDelivery?: BeforeInteractiveDelivery,
|
|
59
|
+
onAcquisitionFailure?: () => Effect.Effect<void>,
|
|
45
60
|
) => Effect.Effect<InteractiveLaunch, HerdrError>
|
|
46
61
|
}
|
|
47
62
|
|
|
@@ -61,22 +76,67 @@ export const paneReadRecentArgs = (paneId: string): string[] => [
|
|
|
61
76
|
"text",
|
|
62
77
|
]
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
const HERDR_QUERY_TIMEOUT_MS = 10_000
|
|
80
|
+
const HERDR_MUTATION_TIMEOUT_MS = 30_000
|
|
81
|
+
const HERDR_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024
|
|
82
|
+
|
|
83
|
+
type HerdrCliResult = { ok: boolean; json: unknown; raw: string }
|
|
84
|
+
|
|
85
|
+
function processRaw(error: ProcessError): string {
|
|
86
|
+
return "stdout" in error ? `${error.stdout}${error.stderr}` : error.message
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function herdrCli(
|
|
90
|
+
processService: ProcessService,
|
|
91
|
+
args: string[],
|
|
92
|
+
options: { mutation?: boolean; timeoutMs?: number } = {},
|
|
93
|
+
): Effect.Effect<HerdrCliResult, HerdrError> {
|
|
94
|
+
const command = shellJoin(["herdr", ...args])
|
|
95
|
+
return Effect.gen(function* () {
|
|
96
|
+
const result = yield* Effect.result(
|
|
97
|
+
processService.run({
|
|
98
|
+
command: "herdr",
|
|
99
|
+
args,
|
|
100
|
+
timeoutMs: options.timeoutMs ?? HERDR_QUERY_TIMEOUT_MS,
|
|
101
|
+
outputLimitBytes: HERDR_OUTPUT_LIMIT_BYTES,
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
if (Result.isFailure(result)) {
|
|
105
|
+
const error = result.failure
|
|
106
|
+
const raw = processRaw(error)
|
|
107
|
+
if (error instanceof ProcessExitError) {
|
|
108
|
+
return { ok: false, json: null, raw }
|
|
109
|
+
}
|
|
110
|
+
const deliveryUnknown =
|
|
111
|
+
options.mutation &&
|
|
112
|
+
(error instanceof ProcessTimeoutError ||
|
|
113
|
+
error instanceof ProcessCancelledError)
|
|
114
|
+
return yield* new HerdrError({
|
|
115
|
+
message: `${command} failed: ${error.message}${raw ? `: ${raw.trim()}` : ""}`,
|
|
116
|
+
command,
|
|
117
|
+
details: {
|
|
118
|
+
...(deliveryUnknown ? { delivery: "unknown" } : {}),
|
|
119
|
+
process_error: error._tag,
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
const raw = `${result.success.stdout}${result.success.stderr}`
|
|
124
|
+
const line = result.success.stdout.trim().split(/\n/).filter(Boolean).pop()
|
|
125
|
+
if (!line) {
|
|
126
|
+
return yield* new HerdrError({
|
|
127
|
+
message: `${command} returned no JSON output`,
|
|
128
|
+
command,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return { ok: true, json: JSON.parse(line), raw }
|
|
133
|
+
} catch {
|
|
134
|
+
return yield* new HerdrError({
|
|
135
|
+
message: `${command} returned malformed JSON: ${raw.trim() || "empty output"}`,
|
|
136
|
+
command,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
68
139
|
})
|
|
69
|
-
const raw = `${r.stdout ?? ""}${r.stderr ?? ""}`
|
|
70
|
-
if (r.status !== 0) {
|
|
71
|
-
return { ok: false, json: null, raw }
|
|
72
|
-
}
|
|
73
|
-
// herdr often prints one JSON object
|
|
74
|
-
const line = (r.stdout ?? "").trim().split(/\n/).filter(Boolean).pop() ?? ""
|
|
75
|
-
try {
|
|
76
|
-
return { ok: true, json: JSON.parse(line), raw }
|
|
77
|
-
} catch {
|
|
78
|
-
return { ok: true, json: null, raw }
|
|
79
|
-
}
|
|
80
140
|
}
|
|
81
141
|
|
|
82
142
|
function resultOf(json: unknown): Record<string, unknown> | null {
|
|
@@ -137,98 +197,157 @@ export function probeHerdrAvailability(
|
|
|
137
197
|
})
|
|
138
198
|
}
|
|
139
199
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
146
|
-
(paneId) => herdrCli(["pane", "get", paneId]),
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function paneGetSync(paneId: string): PaneInfo {
|
|
151
|
-
const r = herdrCli(["pane", "get", paneId])
|
|
152
|
-
if (!r.ok) return { ok: false }
|
|
153
|
-
const res = resultOf(r.json)
|
|
154
|
-
const pane = (res?.pane as Record<string, unknown>) ?? {}
|
|
155
|
-
return {
|
|
156
|
-
ok: true,
|
|
157
|
-
agent_status: pane.agent_status ? String(pane.agent_status) : undefined,
|
|
158
|
-
label: pane.label ? String(pane.label) : undefined,
|
|
159
|
-
agent: pane.agent ? String(pane.agent) : undefined,
|
|
200
|
+
function herdrAvailability(
|
|
201
|
+
processService: ProcessService,
|
|
202
|
+
): Effect.Effect<HerdrAvailability, HerdrError> {
|
|
203
|
+
if (process.env.HERDR_ENV !== "1" || !process.env.HERDR_PANE_ID) {
|
|
204
|
+
return Effect.succeed("unavailable")
|
|
160
205
|
}
|
|
206
|
+
const current = process.env.HERDR_PANE_ID
|
|
207
|
+
return Effect.gen(function* () {
|
|
208
|
+
const r = yield* herdrCli(processService, ["pane", "get", current])
|
|
209
|
+
if (r.ok) return "available"
|
|
210
|
+
if (/pane_not_found|pane not found/i.test(r.raw)) return "unavailable"
|
|
211
|
+
return yield* new HerdrError({
|
|
212
|
+
message: `failed to verify current Herdr pane ${current}: ${r.raw.trim() || "unknown herdr error"}`,
|
|
213
|
+
command: "herdr pane get",
|
|
214
|
+
})
|
|
215
|
+
})
|
|
161
216
|
}
|
|
162
217
|
|
|
163
|
-
function
|
|
164
|
-
|
|
218
|
+
export function paneGet(
|
|
219
|
+
processService: ProcessService,
|
|
220
|
+
paneId: string,
|
|
221
|
+
): Effect.Effect<PaneInfo, HerdrError> {
|
|
222
|
+
return Effect.gen(function* () {
|
|
223
|
+
const r = yield* herdrCli(processService, ["pane", "get", paneId])
|
|
224
|
+
if (!r.ok) {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
missing: /pane_not_found|pane not found/i.test(r.raw),
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const res = resultOf(r.json)
|
|
231
|
+
if (!res?.pane || typeof res.pane !== "object") {
|
|
232
|
+
return yield* new HerdrError({
|
|
233
|
+
message: `herdr pane get returned no pane for ${paneId}`,
|
|
234
|
+
command: "herdr pane get",
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
const pane = res.pane as Record<string, unknown>
|
|
238
|
+
return {
|
|
239
|
+
ok: true,
|
|
240
|
+
agent_status: pane.agent_status ? String(pane.agent_status) : undefined,
|
|
241
|
+
label: pane.label ? String(pane.label) : undefined,
|
|
242
|
+
agent: pane.agent ? String(pane.agent) : undefined,
|
|
243
|
+
}
|
|
244
|
+
})
|
|
165
245
|
}
|
|
166
246
|
|
|
167
|
-
function
|
|
247
|
+
function paneReadRecent(
|
|
248
|
+
processService: ProcessService,
|
|
249
|
+
paneId: string,
|
|
250
|
+
): Effect.Effect<string, HerdrError> {
|
|
168
251
|
const args = paneReadRecentArgs(paneId)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
252
|
+
return Effect.gen(function* () {
|
|
253
|
+
const r = yield* Effect.result(
|
|
254
|
+
processService.run({
|
|
255
|
+
command: "herdr",
|
|
256
|
+
args,
|
|
257
|
+
timeoutMs: HERDR_QUERY_TIMEOUT_MS,
|
|
258
|
+
outputLimitBytes: HERDR_OUTPUT_LIMIT_BYTES,
|
|
259
|
+
}),
|
|
260
|
+
)
|
|
261
|
+
if (Result.isFailure(r)) {
|
|
262
|
+
const output = processRaw(r.failure)
|
|
263
|
+
.trim()
|
|
264
|
+
.split(/\r?\n/)
|
|
265
|
+
.slice(-80)
|
|
266
|
+
.join("\n")
|
|
267
|
+
throw new HerdrError({
|
|
268
|
+
message: `herdr pane read failed for ${paneId}${output ? `: ${output}` : ""}`,
|
|
269
|
+
command: shellJoin(["herdr", ...args]),
|
|
270
|
+
...(output ? { details: { output } } : {}),
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
return r.success.stdout
|
|
172
274
|
})
|
|
173
|
-
if (r.status !== 0 || r.error) {
|
|
174
|
-
const output = `${r.stdout ?? ""}${r.stderr ?? ""}${r.error?.message ?? ""}`
|
|
175
|
-
.trim()
|
|
176
|
-
.split(/\r?\n/)
|
|
177
|
-
.slice(-80)
|
|
178
|
-
.join("\n")
|
|
179
|
-
throw new HerdrError({
|
|
180
|
-
message: `herdr pane read failed for ${paneId}${output ? `: ${output}` : ""}`,
|
|
181
|
-
command: shellJoin(["herdr", ...args]),
|
|
182
|
-
...(output ? { details: { output } } : {}),
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
return r.stdout ?? ""
|
|
186
275
|
}
|
|
187
276
|
|
|
188
277
|
/** Prefer right on wide panes, down on tall/narrow ones. */
|
|
189
|
-
function
|
|
278
|
+
function splitDirection(
|
|
279
|
+
processService: ProcessService,
|
|
280
|
+
): Effect.Effect<"right" | "down", HerdrError> {
|
|
190
281
|
const current = process.env.HERDR_PANE_ID
|
|
191
|
-
if (!current) return "right"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
282
|
+
if (!current) return Effect.succeed("right")
|
|
283
|
+
return Effect.gen(function* () {
|
|
284
|
+
const r = yield* herdrCli(processService, [
|
|
285
|
+
"pane",
|
|
286
|
+
"layout",
|
|
287
|
+
"--pane",
|
|
288
|
+
current,
|
|
289
|
+
])
|
|
290
|
+
const res = resultOf(r.json)
|
|
291
|
+
const layout = res?.layout as Record<string, unknown> | undefined
|
|
292
|
+
if (!Array.isArray(layout?.panes)) {
|
|
293
|
+
return yield* new HerdrError({
|
|
294
|
+
message: "herdr pane layout returned no panes",
|
|
295
|
+
command: "herdr pane layout",
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
const panes = layout.panes as Array<Record<string, unknown>>
|
|
299
|
+
const me = panes.find((p) => String(p.pane_id) === current)
|
|
300
|
+
const rect = me?.rect as { width?: number; height?: number } | undefined
|
|
301
|
+
if (rect?.width != null && rect?.height != null) {
|
|
302
|
+
return rect.width >= rect.height ? "right" : "down"
|
|
303
|
+
}
|
|
304
|
+
return "right"
|
|
305
|
+
})
|
|
202
306
|
}
|
|
203
307
|
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
308
|
+
function splitPane(
|
|
309
|
+
processService: ProcessService,
|
|
310
|
+
): Effect.Effect<string, HerdrError> {
|
|
311
|
+
return Effect.gen(function* () {
|
|
312
|
+
const direction = yield* splitDirection(processService)
|
|
313
|
+
const r = yield* herdrCli(
|
|
314
|
+
processService,
|
|
315
|
+
["pane", "split", "--current", "--direction", direction, "--no-focus"],
|
|
316
|
+
{ mutation: true },
|
|
317
|
+
)
|
|
318
|
+
if (!r.ok)
|
|
319
|
+
return yield* new HerdrError({
|
|
320
|
+
message: `herdr pane split failed: ${r.raw}`,
|
|
321
|
+
})
|
|
322
|
+
const res = resultOf(r.json)
|
|
323
|
+
const pane = res?.pane as Record<string, unknown> | undefined
|
|
324
|
+
const id = pane?.pane_id ? String(pane.pane_id) : null
|
|
325
|
+
if (!id) {
|
|
326
|
+
return yield* new HerdrError({
|
|
327
|
+
message: `herdr pane split: no pane_id in ${r.raw}`,
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
return id
|
|
331
|
+
})
|
|
225
332
|
}
|
|
226
333
|
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
334
|
+
function renamePane(
|
|
335
|
+
processService: ProcessService,
|
|
336
|
+
paneId: string,
|
|
337
|
+
label: string,
|
|
338
|
+
): Effect.Effect<void, HerdrError> {
|
|
339
|
+
return Effect.gen(function* () {
|
|
340
|
+
const r = yield* herdrCli(
|
|
341
|
+
processService,
|
|
342
|
+
["pane", "rename", paneId, label],
|
|
343
|
+
{ mutation: true },
|
|
344
|
+
)
|
|
345
|
+
if (!r.ok) {
|
|
346
|
+
return yield* new HerdrError({
|
|
347
|
+
message: `herdr pane rename failed: ${r.raw}`,
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
})
|
|
232
351
|
}
|
|
233
352
|
|
|
234
353
|
/**
|
|
@@ -236,85 +355,97 @@ function renamePaneSync(paneId: string, label: string): void {
|
|
|
236
355
|
* When a live agent TUI is focused, this submits a prompt (not a shell command).
|
|
237
356
|
* When the pane is a bare shell, this runs a shell line.
|
|
238
357
|
*/
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
358
|
+
function paneRun(
|
|
359
|
+
processService: ProcessService,
|
|
360
|
+
paneId: string,
|
|
361
|
+
command: string,
|
|
362
|
+
): Effect.Effect<void, HerdrError> {
|
|
363
|
+
return Effect.gen(function* () {
|
|
364
|
+
const r = yield* herdrCli(
|
|
365
|
+
processService,
|
|
366
|
+
["pane", "run", paneId, command],
|
|
367
|
+
{ mutation: true },
|
|
368
|
+
)
|
|
369
|
+
if (!r.ok) {
|
|
370
|
+
return yield* new HerdrError({
|
|
371
|
+
message: `herdr pane run failed: ${r.raw}`,
|
|
372
|
+
command: "herdr pane run",
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
})
|
|
247
376
|
}
|
|
248
377
|
|
|
249
378
|
/** Send raw key names (e.g. Escape, Enter) into a pane. */
|
|
250
|
-
function
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
379
|
+
function paneSendKeys(
|
|
380
|
+
processService: ProcessService,
|
|
381
|
+
paneId: string,
|
|
382
|
+
keys: string[],
|
|
383
|
+
): Effect.Effect<void, HerdrError> {
|
|
384
|
+
if (keys.length === 0) return Effect.void
|
|
385
|
+
return Effect.gen(function* () {
|
|
386
|
+
const r = yield* herdrCli(
|
|
387
|
+
processService,
|
|
388
|
+
["pane", "send-keys", paneId, ...keys],
|
|
389
|
+
{ mutation: true },
|
|
390
|
+
)
|
|
391
|
+
if (!r.ok) {
|
|
392
|
+
return yield* new HerdrError({
|
|
393
|
+
message: `herdr pane send-keys failed: ${r.raw}`,
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
})
|
|
256
397
|
}
|
|
257
398
|
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
399
|
+
function paneForegroundNames(
|
|
400
|
+
processService: ProcessService,
|
|
401
|
+
paneId: string,
|
|
402
|
+
): Effect.Effect<string[]> {
|
|
403
|
+
return Effect.gen(function* () {
|
|
404
|
+
const r = yield* herdrCli(processService, [
|
|
405
|
+
"pane",
|
|
406
|
+
"process-info",
|
|
407
|
+
"--pane",
|
|
408
|
+
paneId,
|
|
409
|
+
])
|
|
410
|
+
const res = resultOf(r.json)
|
|
411
|
+
const processInfo = res?.process_info as Record<string, unknown> | undefined
|
|
412
|
+
if (!Array.isArray(processInfo?.foreground_processes)) {
|
|
413
|
+
return yield* new HerdrError({
|
|
414
|
+
message: "herdr pane process-info returned no foreground_processes",
|
|
415
|
+
command: "herdr pane process-info",
|
|
416
|
+
})
|
|
276
417
|
}
|
|
277
|
-
const procs =
|
|
418
|
+
const procs = processInfo.foreground_processes as Array<{
|
|
419
|
+
name?: string
|
|
420
|
+
argv0?: string
|
|
421
|
+
cmdline?: string
|
|
422
|
+
}>
|
|
278
423
|
return procs.map((p) => p.cmdline || p.argv0 || p.name || "?")
|
|
279
|
-
}
|
|
280
|
-
return []
|
|
281
|
-
}
|
|
424
|
+
}).pipe(Effect.catch(() => Effect.succeed([])))
|
|
282
425
|
}
|
|
283
426
|
|
|
284
|
-
function toHerdrError(
|
|
285
|
-
return
|
|
286
|
-
?
|
|
287
|
-
: new HerdrError({
|
|
427
|
+
function toHerdrError(error: unknown): HerdrError {
|
|
428
|
+
return error instanceof HerdrError
|
|
429
|
+
? error
|
|
430
|
+
: new HerdrError({
|
|
431
|
+
message: error instanceof Error ? error.message : String(error),
|
|
432
|
+
})
|
|
288
433
|
}
|
|
289
434
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
* `Effect.gen` is a defect, and defects pass straight through `Effect.ignore` /
|
|
293
|
-
* `Effect.option` — so every sync herdr call must go through `Effect.try` for
|
|
294
|
-
* best-effort recovery blocks to actually be best-effort.
|
|
295
|
-
*/
|
|
296
|
-
function paneRun(
|
|
435
|
+
function paneClose(
|
|
436
|
+
processService: ProcessService,
|
|
297
437
|
paneId: string,
|
|
298
|
-
command: string,
|
|
299
438
|
): Effect.Effect<void, HerdrError> {
|
|
300
|
-
return Effect.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (!r.ok) {
|
|
311
|
-
throw new HerdrError({
|
|
312
|
-
message: `herdr pane close failed: ${r.raw}`,
|
|
313
|
-
command: "herdr pane close",
|
|
314
|
-
})
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
catch: toHerdrError,
|
|
439
|
+
return Effect.gen(function* () {
|
|
440
|
+
const r = yield* herdrCli(processService, ["pane", "close", paneId], {
|
|
441
|
+
mutation: true,
|
|
442
|
+
})
|
|
443
|
+
if (!r.ok) {
|
|
444
|
+
return yield* new HerdrError({
|
|
445
|
+
message: `herdr pane close failed: ${r.raw}`,
|
|
446
|
+
command: "herdr pane close",
|
|
447
|
+
})
|
|
448
|
+
}
|
|
318
449
|
})
|
|
319
450
|
}
|
|
320
451
|
|
|
@@ -333,7 +464,7 @@ function withLaunchDetails(
|
|
|
333
464
|
export function cleanupFailedInteractiveLaunch(
|
|
334
465
|
error: HerdrError,
|
|
335
466
|
paneId: string,
|
|
336
|
-
close: (paneId: string) => Effect.Effect<void, HerdrError
|
|
467
|
+
close: (paneId: string) => Effect.Effect<void, HerdrError>,
|
|
337
468
|
): Effect.Effect<never, HerdrError> {
|
|
338
469
|
return Effect.gen(function* () {
|
|
339
470
|
const cleanup = yield* Effect.result(close(paneId))
|
|
@@ -349,16 +480,6 @@ export function cleanupFailedInteractiveLaunch(
|
|
|
349
480
|
})
|
|
350
481
|
}
|
|
351
482
|
|
|
352
|
-
function sendKeys(
|
|
353
|
-
paneId: string,
|
|
354
|
-
keys: string[],
|
|
355
|
-
): Effect.Effect<void, HerdrError> {
|
|
356
|
-
return Effect.try({
|
|
357
|
-
try: () => paneSendKeysSync(paneId, keys),
|
|
358
|
-
catch: toHerdrError,
|
|
359
|
-
})
|
|
360
|
-
}
|
|
361
|
-
|
|
362
483
|
/** Unique label for a role slot (stable for the run when we reuse the pane). */
|
|
363
484
|
function roleLabel(role: string, millis: number): string {
|
|
364
485
|
const id = `${millis.toString(36)}-${Math.random().toString(36).slice(2, 6)}`
|
|
@@ -370,23 +491,28 @@ function roleLabel(role: string, millis: number): string {
|
|
|
370
491
|
* Uses herdr wait when available; falls back to poll.
|
|
371
492
|
*/
|
|
372
493
|
function waitAgentReady(
|
|
494
|
+
processService: ProcessService,
|
|
373
495
|
paneId: string,
|
|
374
496
|
timeoutMs = 90_000,
|
|
375
|
-
): Effect.Effect<string | undefined> {
|
|
497
|
+
): Effect.Effect<string | undefined, HerdrError> {
|
|
376
498
|
return Effect.gen(function* () {
|
|
377
499
|
// Prefer Herdr's blocking wait (does not freeze our caller if we use it
|
|
378
500
|
// only for short readiness; dispatch is already a tool call).
|
|
379
|
-
const r = herdrCli(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
501
|
+
const r = yield* herdrCli(
|
|
502
|
+
processService,
|
|
503
|
+
[
|
|
504
|
+
"wait",
|
|
505
|
+
"agent-status",
|
|
506
|
+
paneId,
|
|
507
|
+
"--status",
|
|
508
|
+
"idle",
|
|
509
|
+
"--timeout",
|
|
510
|
+
String(timeoutMs),
|
|
511
|
+
],
|
|
512
|
+
{ timeoutMs: timeoutMs + 5_000 },
|
|
513
|
+
)
|
|
388
514
|
if (r.ok) {
|
|
389
|
-
const s =
|
|
515
|
+
const s = (yield* paneGet(processService, paneId)).agent_status
|
|
390
516
|
if (s === "idle" || s === "done") return s
|
|
391
517
|
}
|
|
392
518
|
// fall back: poll (done also counts as ready). Clock, not Date.now(): the
|
|
@@ -395,32 +521,34 @@ function waitAgentReady(
|
|
|
395
521
|
const deadline =
|
|
396
522
|
(yield* Clock.currentTimeMillis) + Math.min(timeoutMs, 30_000)
|
|
397
523
|
while ((yield* Clock.currentTimeMillis) < deadline) {
|
|
398
|
-
const s =
|
|
524
|
+
const s = (yield* paneGet(processService, paneId)).agent_status
|
|
399
525
|
if (s === "idle" || s === "done") return s
|
|
400
526
|
yield* Effect.sleep(500)
|
|
401
527
|
}
|
|
402
|
-
return
|
|
528
|
+
return (yield* paneGet(processService, paneId)).agent_status
|
|
403
529
|
})
|
|
404
530
|
}
|
|
405
531
|
|
|
406
532
|
/**
|
|
407
533
|
* The three pane operations the recovery ladder drives.
|
|
408
534
|
*
|
|
409
|
-
* Injectable
|
|
410
|
-
* resolves binaries against the process's real PATH and ignores mutations to
|
|
411
|
-
* `process.env.PATH`, so a fake `herdr` placed on a temp PATH is never invoked.
|
|
535
|
+
* Injectable so the recovery ladder can be tested without a Herdr process.
|
|
412
536
|
*/
|
|
413
537
|
export type PromptProbes = {
|
|
414
|
-
readonly status: () => string | undefined
|
|
538
|
+
readonly status: () => Effect.Effect<string | undefined, HerdrError>
|
|
415
539
|
readonly sendKeys: (keys: string[]) => Effect.Effect<void, HerdrError>
|
|
416
540
|
readonly run: (text: string) => Effect.Effect<void, HerdrError>
|
|
417
541
|
}
|
|
418
542
|
|
|
419
|
-
function livePromptProbes(
|
|
543
|
+
function livePromptProbes(
|
|
544
|
+
processService: ProcessService,
|
|
545
|
+
paneId: string,
|
|
546
|
+
): PromptProbes {
|
|
420
547
|
return {
|
|
421
|
-
status: () =>
|
|
422
|
-
|
|
423
|
-
|
|
548
|
+
status: () =>
|
|
549
|
+
Effect.map(paneGet(processService, paneId), (info) => info.agent_status),
|
|
550
|
+
sendKeys: (keys) => paneSendKeys(processService, paneId, keys),
|
|
551
|
+
run: (text) => paneRun(processService, paneId, text),
|
|
424
552
|
}
|
|
425
553
|
}
|
|
426
554
|
|
|
@@ -437,27 +565,42 @@ export function ensurePromptSubmitted(
|
|
|
437
565
|
settleMs?: number
|
|
438
566
|
workingWaitMs?: number
|
|
439
567
|
probes?: PromptProbes
|
|
568
|
+
processService?: ProcessService
|
|
440
569
|
},
|
|
441
|
-
): Effect.Effect<
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
570
|
+
): Effect.Effect<
|
|
571
|
+
{
|
|
572
|
+
accepted: boolean
|
|
573
|
+
attempts: number
|
|
574
|
+
last_status?: string
|
|
575
|
+
},
|
|
576
|
+
HerdrError
|
|
577
|
+
> {
|
|
446
578
|
return Effect.gen(function* () {
|
|
447
|
-
const probes =
|
|
579
|
+
const probes =
|
|
580
|
+
opts?.probes ??
|
|
581
|
+
(opts?.processService
|
|
582
|
+
? livePromptProbes(opts.processService, paneId)
|
|
583
|
+
: undefined)
|
|
584
|
+
if (!probes) {
|
|
585
|
+
return yield* new HerdrError({
|
|
586
|
+
message: "prompt probes or process service are required",
|
|
587
|
+
})
|
|
588
|
+
}
|
|
448
589
|
const settleMs = opts?.settleMs ?? 2500
|
|
449
590
|
const workingWaitMs = opts?.workingWaitMs ?? 12_000
|
|
450
591
|
let attempts = 1
|
|
451
592
|
|
|
452
|
-
const waitForWorking = (
|
|
593
|
+
const waitForWorking = (
|
|
594
|
+
ms: number,
|
|
595
|
+
): Effect.Effect<string | undefined, HerdrError> =>
|
|
453
596
|
Effect.gen(function* () {
|
|
454
597
|
const deadline = (yield* Clock.currentTimeMillis) + ms
|
|
455
598
|
while ((yield* Clock.currentTimeMillis) < deadline) {
|
|
456
|
-
const s = probes.status()
|
|
599
|
+
const s = yield* probes.status()
|
|
457
600
|
if (s === "working" || s === "blocked") return s
|
|
458
601
|
yield* Effect.sleep(400)
|
|
459
602
|
}
|
|
460
|
-
return probes.status()
|
|
603
|
+
return yield* probes.status()
|
|
461
604
|
})
|
|
462
605
|
|
|
463
606
|
// Give the first paneRun a moment to flip status.
|
|
@@ -498,7 +641,7 @@ export function ensurePromptSubmitted(
|
|
|
498
641
|
return {
|
|
499
642
|
accepted: false,
|
|
500
643
|
attempts,
|
|
501
|
-
last_status: probes.status(),
|
|
644
|
+
last_status: yield* probes.status(),
|
|
502
645
|
}
|
|
503
646
|
}
|
|
504
647
|
yield* Effect.sleep(settleMs)
|
|
@@ -519,12 +662,15 @@ export function ensurePromptSubmitted(
|
|
|
519
662
|
* Never claims an unrelated pane by scanning labels alone.
|
|
520
663
|
*/
|
|
521
664
|
function acquireRolePane(
|
|
665
|
+
processService: ProcessService,
|
|
522
666
|
role: string,
|
|
523
667
|
hostAdapter: ApneaHostAdapter,
|
|
524
668
|
opts?: {
|
|
525
669
|
prefer?: RolePaneRef | null
|
|
526
670
|
/** Launch interactive harness only when creating a new pane */
|
|
527
671
|
interactiveCmd?: string[]
|
|
672
|
+
beforeDelivery?: BeforeInteractiveDelivery
|
|
673
|
+
onAcquisitionFailure?: () => Effect.Effect<void>
|
|
528
674
|
},
|
|
529
675
|
): Effect.Effect<RolePaneRef & { reused: boolean }, HerdrError> {
|
|
530
676
|
return Effect.gen(function* () {
|
|
@@ -534,7 +680,13 @@ function acquireRolePane(
|
|
|
534
680
|
})
|
|
535
681
|
}
|
|
536
682
|
|
|
537
|
-
if (
|
|
683
|
+
if (
|
|
684
|
+
opts?.prefer?.pane_id &&
|
|
685
|
+
(yield* paneGet(processService, opts.prefer.pane_id)).ok
|
|
686
|
+
) {
|
|
687
|
+
if (opts.beforeDelivery) {
|
|
688
|
+
yield* Effect.uninterruptible(opts.beforeDelivery(opts.prefer))
|
|
689
|
+
}
|
|
538
690
|
return {
|
|
539
691
|
pane_id: opts.prefer.pane_id,
|
|
540
692
|
label: opts.prefer.label,
|
|
@@ -544,25 +696,44 @@ function acquireRolePane(
|
|
|
544
696
|
|
|
545
697
|
const millis = yield* Clock.currentTimeMillis
|
|
546
698
|
const label = roleLabel(role, millis)
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
699
|
+
// A split can create a pane before its response arrives. Finish the bounded
|
|
700
|
+
// acquisition and ownership save before observing cancellation.
|
|
701
|
+
const paneId = yield* Effect.uninterruptible(
|
|
702
|
+
Effect.gen(function* () {
|
|
703
|
+
const split = yield* Effect.result(splitPane(processService))
|
|
704
|
+
if (Result.isFailure(split)) {
|
|
705
|
+
// Restore proven non-delivery before a pending interruption can hide
|
|
706
|
+
// the split failure when this acquisition mask exits.
|
|
707
|
+
if (split.failure.details?.delivery !== "unknown") {
|
|
708
|
+
yield* opts?.onAcquisitionFailure?.() ?? Effect.void
|
|
709
|
+
}
|
|
710
|
+
return yield* withLaunchDetails(split.failure, {
|
|
711
|
+
delivery:
|
|
712
|
+
split.failure.details?.delivery === "unknown"
|
|
713
|
+
? "unknown"
|
|
714
|
+
: "not_delivered",
|
|
715
|
+
newly_created: false,
|
|
716
|
+
})
|
|
717
|
+
}
|
|
718
|
+
const paneId = split.success
|
|
719
|
+
if (opts?.beforeDelivery) {
|
|
720
|
+
const persisted = yield* Effect.result(
|
|
721
|
+
opts.beforeDelivery({ pane_id: paneId, label }),
|
|
722
|
+
)
|
|
723
|
+
if (Result.isFailure(persisted)) {
|
|
724
|
+
return yield* cleanupFailedInteractiveLaunch(
|
|
725
|
+
persisted.failure,
|
|
726
|
+
paneId,
|
|
727
|
+
(id) => paneClose(processService, id),
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return paneId
|
|
551
732
|
}),
|
|
552
733
|
)
|
|
553
|
-
if (Result.isFailure(split)) {
|
|
554
|
-
return yield* withLaunchDetails(split.failure, {
|
|
555
|
-
delivery: "not_delivered",
|
|
556
|
-
newly_created: false,
|
|
557
|
-
})
|
|
558
|
-
}
|
|
559
|
-
const paneId = split.success
|
|
560
734
|
const prepared = yield* Effect.result(
|
|
561
735
|
Effect.gen(function* () {
|
|
562
|
-
yield*
|
|
563
|
-
try: () => renamePaneSync(paneId, label),
|
|
564
|
-
catch: toHerdrError,
|
|
565
|
-
})
|
|
736
|
+
yield* renamePane(processService, paneId, label)
|
|
566
737
|
if (!opts?.interactiveCmd?.length) return
|
|
567
738
|
// Launch the interactive harness only (no task argv).
|
|
568
739
|
// Pi roles get PI_CODING_AGENT_DIR without pi-vimmode so pane-run pastes
|
|
@@ -576,11 +747,23 @@ function acquireRolePane(
|
|
|
576
747
|
catch: toHerdrError,
|
|
577
748
|
})
|
|
578
749
|
const cmd = shellJoin(["cd", process.cwd(), "&&", "exec", ...launchCmd])
|
|
579
|
-
yield* paneRun(paneId, cmd)
|
|
750
|
+
yield* paneRun(processService, paneId, cmd)
|
|
580
751
|
}),
|
|
581
752
|
)
|
|
582
753
|
if (Result.isFailure(prepared)) {
|
|
583
|
-
|
|
754
|
+
if (prepared.failure.details?.delivery === "unknown") {
|
|
755
|
+
return yield* withLaunchDetails(prepared.failure, {
|
|
756
|
+
delivery: "unknown",
|
|
757
|
+
pane_id: paneId,
|
|
758
|
+
pane_label: label,
|
|
759
|
+
newly_created: true,
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
return yield* cleanupFailedInteractiveLaunch(
|
|
763
|
+
prepared.failure,
|
|
764
|
+
paneId,
|
|
765
|
+
(id) => paneClose(processService, id),
|
|
766
|
+
)
|
|
584
767
|
}
|
|
585
768
|
return { pane_id: paneId, label, reused: false }
|
|
586
769
|
})
|
|
@@ -594,17 +777,20 @@ function acquireRolePane(
|
|
|
594
777
|
* `claude -p` / `pi -p` dumping shell output.
|
|
595
778
|
*/
|
|
596
779
|
function runInteractivePromptImpl(
|
|
780
|
+
processService: ProcessService,
|
|
597
781
|
hostAdapter: ApneaHostAdapter,
|
|
598
782
|
role: string,
|
|
599
783
|
interactiveCmd: string[],
|
|
600
784
|
prompt: string,
|
|
601
785
|
prefer: RolePaneRef | null,
|
|
786
|
+
beforeDelivery?: BeforeInteractiveDelivery,
|
|
787
|
+
onAcquisitionFailure?: () => Effect.Effect<void>,
|
|
602
788
|
): Effect.Effect<InteractiveLaunch, HerdrError> {
|
|
603
789
|
return Effect.gen(function* () {
|
|
604
790
|
let preferUse: RolePaneRef | null = null
|
|
605
791
|
if (prefer?.pane_id) {
|
|
606
792
|
// One `pane get`: liveness and agent_status come from the same call.
|
|
607
|
-
const info =
|
|
793
|
+
const info = yield* paneGet(processService, prefer.pane_id)
|
|
608
794
|
// reuse only when a live agent can take a new prompt
|
|
609
795
|
// working/blocked/unknown/shell-only → new pane
|
|
610
796
|
if (
|
|
@@ -615,38 +801,64 @@ function runInteractivePromptImpl(
|
|
|
615
801
|
}
|
|
616
802
|
}
|
|
617
803
|
|
|
618
|
-
const acquired = yield* acquireRolePane(role, hostAdapter, {
|
|
804
|
+
const acquired = yield* acquireRolePane(processService, role, hostAdapter, {
|
|
619
805
|
prefer: preferUse,
|
|
620
806
|
interactiveCmd: preferUse ? undefined : interactiveCmd,
|
|
807
|
+
beforeDelivery,
|
|
808
|
+
onAcquisitionFailure,
|
|
621
809
|
})
|
|
622
810
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
811
|
+
const ready = yield* Effect.result(
|
|
812
|
+
Effect.gen(function* () {
|
|
813
|
+
if (!acquired.reused) {
|
|
814
|
+
yield* waitAgentReady(processService, acquired.pane_id, 90_000)
|
|
815
|
+
// Some harnesses accept input before status settles.
|
|
816
|
+
} else {
|
|
817
|
+
const st = (yield* paneGet(processService, acquired.pane_id))
|
|
818
|
+
.agent_status
|
|
819
|
+
if (st !== "idle" && st !== "done") {
|
|
820
|
+
yield* waitAgentReady(processService, acquired.pane_id, 30_000)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
633
823
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
824
|
+
const beforePrompt =
|
|
825
|
+
hostAdapter.beforeInteractivePrompt?.(interactiveCmd)
|
|
826
|
+
if (beforePrompt) {
|
|
827
|
+
// Host preparation is best-effort; command wrapping is the primary guard.
|
|
828
|
+
yield* Effect.gen(function* () {
|
|
829
|
+
yield* paneRun(processService, acquired.pane_id, beforePrompt)
|
|
830
|
+
yield* waitAgentReady(processService, acquired.pane_id, 5_000)
|
|
831
|
+
yield* Effect.sleep(300)
|
|
832
|
+
}).pipe(Effect.ignore)
|
|
833
|
+
}
|
|
834
|
+
}),
|
|
835
|
+
)
|
|
836
|
+
if (Result.isFailure(ready)) {
|
|
837
|
+
if (!acquired.reused) {
|
|
838
|
+
return yield* cleanupFailedInteractiveLaunch(
|
|
839
|
+
ready.failure,
|
|
840
|
+
acquired.pane_id,
|
|
841
|
+
(id) => paneClose(processService, id),
|
|
842
|
+
)
|
|
843
|
+
}
|
|
844
|
+
return yield* withLaunchDetails(ready.failure, {
|
|
845
|
+
delivery: "not_delivered",
|
|
846
|
+
})
|
|
642
847
|
}
|
|
643
848
|
|
|
644
849
|
// Submit pointer into the live TUI (Herdr: pane run = text + Enter),
|
|
645
850
|
// then confirm the agent actually started — do not trust fire-and-forget.
|
|
646
|
-
const submitted = yield* Effect.result(
|
|
851
|
+
const submitted = yield* Effect.result(
|
|
852
|
+
Effect.gen(function* () {
|
|
853
|
+
yield* paneRun(processService, acquired.pane_id, prompt)
|
|
854
|
+
return yield* ensurePromptSubmitted(acquired.pane_id, prompt, {
|
|
855
|
+
processService,
|
|
856
|
+
})
|
|
857
|
+
}),
|
|
858
|
+
)
|
|
647
859
|
if (Result.isFailure(submitted)) {
|
|
648
860
|
return yield* withLaunchDetails(submitted.failure, {
|
|
649
|
-
//
|
|
861
|
+
// Submission or acceptance probing can fail after the pane accepted text.
|
|
650
862
|
// Closing or retrying here could kill or duplicate a live worker.
|
|
651
863
|
delivery: "unknown",
|
|
652
864
|
pane_id: acquired.pane_id,
|
|
@@ -654,7 +866,7 @@ function runInteractivePromptImpl(
|
|
|
654
866
|
reused: acquired.reused,
|
|
655
867
|
})
|
|
656
868
|
}
|
|
657
|
-
const submit =
|
|
869
|
+
const submit = submitted.success
|
|
658
870
|
return {
|
|
659
871
|
pane_id: acquired.pane_id,
|
|
660
872
|
label: acquired.label,
|
|
@@ -673,32 +885,26 @@ function runInteractivePromptImpl(
|
|
|
673
885
|
export const makeHerdrLive = (hostAdapter: ApneaHostAdapter) =>
|
|
674
886
|
Layer.effect(
|
|
675
887
|
Herdr,
|
|
676
|
-
Effect.
|
|
677
|
-
|
|
888
|
+
Effect.gen(function* () {
|
|
889
|
+
const processService = yield* Process
|
|
890
|
+
return Herdr.of({
|
|
678
891
|
enabled: Effect.sync(herdrEnabledSync),
|
|
679
892
|
|
|
680
|
-
availability:
|
|
681
|
-
try: herdrAvailabilitySync,
|
|
682
|
-
catch: toHerdrError,
|
|
683
|
-
}),
|
|
893
|
+
availability: herdrAvailability(processService),
|
|
684
894
|
|
|
685
|
-
paneGet: (paneId) =>
|
|
895
|
+
paneGet: (paneId) => paneGet(processService, paneId),
|
|
686
896
|
|
|
687
|
-
paneRun,
|
|
897
|
+
paneRun: (paneId, command) => paneRun(processService, paneId, command),
|
|
688
898
|
|
|
689
|
-
paneReadRecent: (paneId) =>
|
|
690
|
-
Effect.try({
|
|
691
|
-
try: () => paneReadRecentSync(paneId),
|
|
692
|
-
catch: toHerdrError,
|
|
693
|
-
}),
|
|
899
|
+
paneReadRecent: (paneId) => paneReadRecent(processService, paneId),
|
|
694
900
|
|
|
695
901
|
paneForegroundNames: (paneId) =>
|
|
696
|
-
|
|
902
|
+
paneForegroundNames(processService, paneId),
|
|
697
903
|
|
|
698
904
|
runInteractivePrompt: (...args) =>
|
|
699
|
-
runInteractivePromptImpl(hostAdapter, ...args),
|
|
700
|
-
})
|
|
701
|
-
),
|
|
905
|
+
runInteractivePromptImpl(processService, hostAdapter, ...args),
|
|
906
|
+
})
|
|
907
|
+
}),
|
|
702
908
|
)
|
|
703
909
|
|
|
704
910
|
export const HerdrLive = makeHerdrLive(neutralHostAdapter)
|