ralphctl 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +13 -11
  2. package/dist/{add-CIM72NE3.mjs → add-DVPVHENV.mjs} +7 -7
  3. package/dist/{add-GX7P7XTT.mjs → add-YVXM34RP.mjs} +6 -5
  4. package/dist/{chunk-GL7MKLLS.mjs → chunk-ACRMBVEE.mjs} +458 -181
  5. package/dist/{chunk-NUYQK5MN.mjs → chunk-BSB4EDGR.mjs} +2 -2
  6. package/dist/{chunk-YCDUVPRT.mjs → chunk-CBMFRQ4Y.mjs} +5 -73
  7. package/dist/{chunk-3QBEBKMZ.mjs → chunk-FNAAA32W.mjs} +7 -7
  8. package/dist/{chunk-JOQO4HMM.mjs → chunk-GQ2WFKBN.mjs} +11 -11
  9. package/dist/{chunk-TKPTT2UG.mjs → chunk-OFILN7QL.mjs} +798 -1023
  10. package/dist/{chunk-7JLZQICD.mjs → chunk-OGEXYSFS.mjs} +7 -7
  11. package/dist/{chunk-D2YGPLIV.mjs → chunk-PYZEQ2VK.mjs} +214 -9
  12. package/dist/{chunk-57UWLHRH.mjs → chunk-VAZ3LJBI.mjs} +12 -1
  13. package/dist/{chunk-CTP2A436.mjs → chunk-WDMLPXOD.mjs} +11 -4
  14. package/dist/{chunk-FKMKOWLA.mjs → chunk-XN2UIHBY.mjs} +84 -3
  15. package/dist/chunk-ZLWSPLWI.mjs +1117 -0
  16. package/dist/cli.mjs +72 -21
  17. package/dist/create-Z635FQKO.mjs +15 -0
  18. package/dist/{handle-BBAZJ44Y.mjs → handle-23EFF3BE.mjs} +1 -1
  19. package/dist/{mount-ISHZM36X.mjs → mount-VEV3TESX.mjs} +1702 -1202
  20. package/dist/{project-2IE7VWDB.mjs → project-DQHF4ISP.mjs} +3 -3
  21. package/dist/prompts/check-script-discover.md +69 -0
  22. package/dist/prompts/repo-onboard.md +111 -0
  23. package/dist/prompts/sprint-feedback.md +4 -0
  24. package/dist/prompts/task-evaluation.md +44 -2
  25. package/dist/prompts/task-execution.md +5 -0
  26. package/dist/{resolver-EOE5WUMV.mjs → resolver-OVPYVW6Q.mjs} +4 -4
  27. package/dist/{sprint-OGOFEJJH.mjs → sprint-4E26AB5F.mjs} +4 -4
  28. package/dist/start-2WH4BTDB.mjs +19 -0
  29. package/package.json +6 -6
  30. package/dist/create-7WFSCMP4.mjs +0 -15
  31. package/dist/start-76JKJQIH.mjs +0 -17
@@ -0,0 +1,1117 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ EXIT_INTERRUPTED
4
+ } from "./chunk-CFUVE2BP.mjs";
5
+ import {
6
+ getPrompt
7
+ } from "./chunk-747KW2RW.mjs";
8
+ import {
9
+ emoji,
10
+ getAiProvider,
11
+ log,
12
+ setAiProvider
13
+ } from "./chunk-XN2UIHBY.mjs";
14
+ import {
15
+ ensureError,
16
+ wrapAsync
17
+ } from "./chunk-IWXBJD2D.mjs";
18
+ import {
19
+ assertSafeCwd
20
+ } from "./chunk-WDMLPXOD.mjs";
21
+ import {
22
+ IOError,
23
+ SpawnError
24
+ } from "./chunk-VAZ3LJBI.mjs";
25
+
26
+ // src/integration/ui/tui/runtime/screen.ts
27
+ var ENTER_ALT_SCREEN = "\x1B[?1049h";
28
+ var LEAVE_ALT_SCREEN = "\x1B[?1049l";
29
+ var HIDE_CURSOR = "\x1B[?25l";
30
+ var SHOW_CURSOR = "\x1B[?25h";
31
+ var CLEAR_SCREEN = "\x1B[2J\x1B[H";
32
+ var altScreenActive = false;
33
+ var safetyNetsInstalled = false;
34
+ function writeRaw(seq) {
35
+ if (process.stdout.isTTY) process.stdout.write(seq);
36
+ }
37
+ function restore() {
38
+ if (!altScreenActive) return;
39
+ altScreenActive = false;
40
+ writeRaw(SHOW_CURSOR);
41
+ writeRaw(LEAVE_ALT_SCREEN);
42
+ }
43
+ function installSafetyNets() {
44
+ if (safetyNetsInstalled) return;
45
+ safetyNetsInstalled = true;
46
+ process.on("exit", restore);
47
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
48
+ process.on(sig, () => {
49
+ restore();
50
+ process.kill(process.pid, sig);
51
+ });
52
+ }
53
+ process.on("uncaughtException", (err) => {
54
+ restore();
55
+ setImmediate(() => {
56
+ throw err;
57
+ });
58
+ });
59
+ }
60
+ function enterAltScreen() {
61
+ if (altScreenActive) return;
62
+ if (!process.stdout.isTTY) return;
63
+ installSafetyNets();
64
+ altScreenActive = true;
65
+ writeRaw(ENTER_ALT_SCREEN);
66
+ writeRaw(CLEAR_SCREEN);
67
+ writeRaw(HIDE_CURSOR);
68
+ }
69
+ function exitAltScreen() {
70
+ restore();
71
+ }
72
+
73
+ // src/integration/ui/tui/runtime/suspend.ts
74
+ var activeInstance = null;
75
+ function registerTuiInstance(instance) {
76
+ activeInstance = instance;
77
+ return () => {
78
+ if (activeInstance === instance) {
79
+ activeInstance = null;
80
+ }
81
+ };
82
+ }
83
+ async function withSuspendedTui(cb) {
84
+ const instance = activeInstance;
85
+ if (instance === null) {
86
+ return cb();
87
+ }
88
+ exitAltScreen();
89
+ try {
90
+ return await cb();
91
+ } finally {
92
+ enterAltScreen();
93
+ instance.clear();
94
+ }
95
+ }
96
+
97
+ // src/integration/ai/session/session.ts
98
+ import { spawn, spawnSync } from "child_process";
99
+
100
+ // src/integration/ai/session/process-manager.ts
101
+ var GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5e3;
102
+ var FORCE_QUIT_WINDOW_MS = 5e3;
103
+ var ProcessManager = class _ProcessManager {
104
+ static instance = null;
105
+ /** All active AI child processes */
106
+ children = /* @__PURE__ */ new Set();
107
+ /** Cleanup callbacks (for stopping spinners, removing temp files) */
108
+ cleanupCallbacks = /* @__PURE__ */ new Set();
109
+ /** Whether we're currently shutting down */
110
+ exiting = false;
111
+ /** Whether signal handlers have been installed */
112
+ handlersInstalled = false;
113
+ /** Timestamp of first SIGINT (for double-signal detection) */
114
+ firstSigintAt = null;
115
+ /** Stored signal handler references for cleanup */
116
+ sigintHandler = null;
117
+ sigtermHandler = null;
118
+ constructor() {
119
+ }
120
+ /**
121
+ * Get the singleton instance.
122
+ */
123
+ static getInstance() {
124
+ _ProcessManager.instance ??= new _ProcessManager();
125
+ return _ProcessManager.instance;
126
+ }
127
+ /**
128
+ * Reset the singleton for testing.
129
+ * @internal
130
+ */
131
+ static resetForTesting() {
132
+ if (_ProcessManager.instance) {
133
+ _ProcessManager.instance.dispose();
134
+ _ProcessManager.instance = null;
135
+ }
136
+ }
137
+ /**
138
+ * Register a child process for tracking.
139
+ * Automatically installs signal handlers on first registration.
140
+ * Throws an error if called during shutdown.
141
+ *
142
+ * @throws Error if called during shutdown
143
+ */
144
+ registerChild(child) {
145
+ if (this.exiting) {
146
+ throw new Error("Cannot register child process during shutdown");
147
+ }
148
+ this.children.add(child);
149
+ child.once("close", () => {
150
+ this.children.delete(child);
151
+ });
152
+ if (!this.handlersInstalled) {
153
+ this.installSignalHandlers();
154
+ this.handlersInstalled = true;
155
+ }
156
+ }
157
+ /**
158
+ * Eagerly install signal handlers without requiring a child registration.
159
+ * Call this at the top of execution loops so Ctrl+C works even before
160
+ * the first AI process is spawned (e.g. while the spinner is visible).
161
+ * Idempotent — safe to call multiple times.
162
+ */
163
+ ensureHandlers() {
164
+ if (!this.handlersInstalled) {
165
+ this.installSignalHandlers();
166
+ this.handlersInstalled = true;
167
+ }
168
+ }
169
+ /**
170
+ * Wire a task-scoped AbortSignal to a terminate callback. When the signal
171
+ * aborts, the callback fires once. Returns a disposer that detaches the
172
+ * listener — call it in a `finally` after the child exits so listeners
173
+ * don't accumulate across repeated runs.
174
+ *
175
+ * If the signal is already aborted, terminate runs on the next microtask
176
+ * so the caller always observes a consistent async flow.
177
+ */
178
+ registerAbort(signal, terminate) {
179
+ let fired = false;
180
+ const handler = () => {
181
+ if (fired) return;
182
+ fired = true;
183
+ try {
184
+ terminate();
185
+ } catch (err) {
186
+ log.error(`Error in abort-signal handler: ${err instanceof Error ? err.message : String(err)}`);
187
+ }
188
+ };
189
+ if (signal.aborted) {
190
+ queueMicrotask(handler);
191
+ }
192
+ signal.addEventListener("abort", handler, { once: true });
193
+ return () => {
194
+ signal.removeEventListener("abort", handler);
195
+ };
196
+ }
197
+ /**
198
+ * Check if a shutdown is in progress.
199
+ * Used by execution loops to break immediately on Ctrl+C.
200
+ */
201
+ isShuttingDown() {
202
+ return this.exiting;
203
+ }
204
+ /**
205
+ * Manually unregister a child process.
206
+ * Normally not needed - children auto-unregister via event listeners.
207
+ */
208
+ unregisterChild(child) {
209
+ this.children.delete(child);
210
+ }
211
+ /**
212
+ * Register a cleanup callback (for spinners, temp files, etc.).
213
+ * Returns a deregister function.
214
+ */
215
+ registerCleanup(callback) {
216
+ this.cleanupCallbacks.add(callback);
217
+ return () => {
218
+ this.cleanupCallbacks.delete(callback);
219
+ };
220
+ }
221
+ /**
222
+ * Kill all tracked child processes with the given signal.
223
+ * Catches errors (ESRCH = already dead, EPERM = permission denied).
224
+ */
225
+ killAll(signal) {
226
+ for (const child of this.children) {
227
+ try {
228
+ child.kill(signal);
229
+ } catch (err) {
230
+ const error = err;
231
+ if (error.code === "ESRCH") {
232
+ this.children.delete(child);
233
+ } else if (error.code === "EPERM") {
234
+ log.warn(`Permission denied killing process ${String(child.pid)}`);
235
+ } else {
236
+ log.error(`Error killing process ${String(child.pid)}: ${error.message}`);
237
+ }
238
+ }
239
+ }
240
+ }
241
+ /**
242
+ * Graceful shutdown sequence:
243
+ * 1. Run all cleanup callbacks (stop spinners)
244
+ * 2. Send SIGINT to all children (what AI CLI processes expect)
245
+ * 3. Wait up to 5 seconds for children to exit
246
+ * 4. Send SIGKILL to any remaining children (force)
247
+ * 5. Exit with code 130 (SIGINT) or 1 (force-quit)
248
+ *
249
+ * Double Ctrl+C: immediate SIGKILL + exit(1)
250
+ */
251
+ async shutdown(signal) {
252
+ if (signal === "SIGINT" && this.firstSigintAt) {
253
+ const now = Date.now();
254
+ if (now - this.firstSigintAt < FORCE_QUIT_WINDOW_MS) {
255
+ log.warn("\n\nForce quit (double signal) \u2014 killing all processes immediately...");
256
+ this.killAll("SIGKILL");
257
+ process.exit(1);
258
+ return;
259
+ }
260
+ }
261
+ if (this.exiting) {
262
+ return;
263
+ }
264
+ this.exiting = true;
265
+ if (signal === "SIGINT") {
266
+ this.firstSigintAt = Date.now();
267
+ }
268
+ log.dim("\n\nShutting down gracefully... (press Ctrl+C again to force-quit)");
269
+ for (const callback of this.cleanupCallbacks) {
270
+ try {
271
+ callback();
272
+ } catch (err) {
273
+ log.error(`Error in cleanup callback: ${err instanceof Error ? err.message : String(err)}`);
274
+ }
275
+ }
276
+ this.cleanupCallbacks.clear();
277
+ this.killAll("SIGINT");
278
+ const waitStart = Date.now();
279
+ while (this.children.size > 0 && Date.now() - waitStart < GRACEFUL_SHUTDOWN_TIMEOUT_MS) {
280
+ await new Promise((resolve) => setTimeout(resolve, 100));
281
+ }
282
+ if (this.children.size > 0) {
283
+ log.warn(`Force-killing ${String(this.children.size)} remaining process(es)...`);
284
+ this.killAll("SIGKILL");
285
+ }
286
+ process.exit(signal === "SIGINT" ? EXIT_INTERRUPTED : 1);
287
+ }
288
+ /**
289
+ * Clean up all resources (for testing).
290
+ * @internal
291
+ */
292
+ dispose() {
293
+ if (this.sigintHandler) {
294
+ process.removeListener("SIGINT", this.sigintHandler);
295
+ this.sigintHandler = null;
296
+ }
297
+ if (this.sigtermHandler) {
298
+ process.removeListener("SIGTERM", this.sigtermHandler);
299
+ this.sigtermHandler = null;
300
+ }
301
+ this.children.clear();
302
+ this.cleanupCallbacks.clear();
303
+ this.exiting = false;
304
+ this.handlersInstalled = false;
305
+ this.firstSigintAt = null;
306
+ }
307
+ /**
308
+ * Install signal handlers for SIGINT and SIGTERM.
309
+ * Uses process.on() (persistent) not process.once() (one-shot).
310
+ * Stores handler references so dispose() can remove them.
311
+ */
312
+ installSignalHandlers() {
313
+ this.sigintHandler = () => {
314
+ void this.shutdown("SIGINT");
315
+ };
316
+ this.sigtermHandler = () => {
317
+ void this.shutdown("SIGTERM");
318
+ };
319
+ process.on("SIGINT", this.sigintHandler);
320
+ process.on("SIGTERM", this.sigtermHandler);
321
+ }
322
+ };
323
+ var processLifecycleAdapter = {
324
+ ensureHandlers: () => {
325
+ ProcessManager.getInstance().ensureHandlers();
326
+ },
327
+ isShuttingDown: () => ProcessManager.getInstance().isShuttingDown(),
328
+ registerAbort: (signal, terminate) => ProcessManager.getInstance().registerAbort(signal, terminate)
329
+ };
330
+
331
+ // src/integration/ai/providers/claude.ts
332
+ import { Result } from "typescript-result";
333
+ var claudeAdapter = {
334
+ name: "claude",
335
+ displayName: "Claude",
336
+ binary: "claude",
337
+ baseArgs: ["--permission-mode", "acceptEdits", "--effort", "xhigh"],
338
+ experimental: false,
339
+ buildInteractiveArgs(prompt, extraArgs = []) {
340
+ return [...this.baseArgs, ...extraArgs, "--", prompt];
341
+ },
342
+ buildHeadlessArgs(extraArgs = []) {
343
+ return ["-p", "--output-format", "json", ...this.baseArgs, ...extraArgs];
344
+ },
345
+ parseJsonOutput(stdout) {
346
+ const jsonResult = Result.try(() => JSON.parse(stdout));
347
+ if (!jsonResult.ok) {
348
+ return { result: stdout, sessionId: null, model: null };
349
+ }
350
+ const parsed = jsonResult.value;
351
+ return {
352
+ result: parsed.result ?? stdout,
353
+ sessionId: parsed.session_id ?? null,
354
+ model: parsed.model ?? null
355
+ };
356
+ },
357
+ buildResumeArgs(sessionId) {
358
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,127}$/.test(sessionId)) {
359
+ throw new Error("Invalid session ID format");
360
+ }
361
+ return ["--resume", sessionId];
362
+ },
363
+ detectRateLimit(stderr) {
364
+ const patterns = [/rate.?limit/i, /\b429\b/, /too many requests/i, /overloaded/i, /\b529\b/];
365
+ const isRateLimited = patterns.some((p) => p.test(stderr));
366
+ if (!isRateLimited) {
367
+ return { rateLimited: false, retryAfterMs: null };
368
+ }
369
+ const retryMatch = /retry.?after:?\s*(\d+)/i.exec(stderr);
370
+ const retryAfterMs = retryMatch?.[1] ? parseInt(retryMatch[1], 10) * 1e3 : null;
371
+ return { rateLimited: true, retryAfterMs };
372
+ },
373
+ getSpawnEnv() {
374
+ return { CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: "1" };
375
+ }
376
+ };
377
+
378
+ // src/integration/ai/providers/copilot.ts
379
+ import { lstat, readdir, unlink } from "fs/promises";
380
+ import { join } from "path";
381
+ import { Result as Result2 } from "typescript-result";
382
+ var copilotAdapter = {
383
+ name: "copilot",
384
+ displayName: "Copilot",
385
+ binary: "copilot",
386
+ experimental: true,
387
+ baseArgs: ["--allow-all-tools"],
388
+ buildInteractiveArgs(prompt, extraArgs = []) {
389
+ return [...this.baseArgs, ...extraArgs, "-i", prompt];
390
+ },
391
+ buildHeadlessArgs(extraArgs = []) {
392
+ return ["-p", "--output-format", "json", "--autopilot", "--no-ask-user", "--share", ...this.baseArgs, ...extraArgs];
393
+ },
394
+ parseJsonOutput(stdout) {
395
+ const lines = stdout.trim().split("\n").filter(Boolean);
396
+ if (lines.length === 0) {
397
+ return { result: "", sessionId: null, model: null };
398
+ }
399
+ const lastLine = lines.at(-1) ?? "";
400
+ const jsonResult = Result2.try(() => JSON.parse(lastLine));
401
+ if (jsonResult.ok) {
402
+ const parsed = jsonResult.value;
403
+ return {
404
+ result: parsed.result ?? parsed.result_text ?? lastLine,
405
+ sessionId: parsed.session_id ?? null,
406
+ model: null
407
+ };
408
+ }
409
+ return { result: stdout.trim(), sessionId: null, model: null };
410
+ },
411
+ buildResumeArgs(sessionId) {
412
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,127}$/.test(sessionId)) {
413
+ throw new Error("Invalid session ID format");
414
+ }
415
+ return [`--resume=${sessionId}`];
416
+ },
417
+ async extractSessionId(cwd) {
418
+ const filesResult = await wrapAsync(
419
+ () => readdir(cwd),
420
+ (err) => new IOError(`Failed to read directory: ${cwd}`, err instanceof Error ? err : void 0)
421
+ );
422
+ if (!filesResult.ok) return null;
423
+ const files = filesResult.value;
424
+ const shareFile = files.find((f) => /^copilot-session-[a-zA-Z0-9_][a-zA-Z0-9_-]*\.md$/.test(f));
425
+ if (!shareFile) return null;
426
+ const match = /^copilot-session-([a-zA-Z0-9_][a-zA-Z0-9_-]{0,127})\.md$/.exec(shareFile);
427
+ if (!match?.[1]) return null;
428
+ const filePath = join(cwd, shareFile);
429
+ const stat = await lstat(filePath).catch(() => null);
430
+ if (stat?.isFile()) {
431
+ await unlink(filePath).catch(() => {
432
+ });
433
+ }
434
+ return match[1];
435
+ },
436
+ detectRateLimit(stderr) {
437
+ const patterns = [/rate.?limit/i, /\b429\b/, /too many requests/i, /overloaded/i, /\b529\b/];
438
+ const isRateLimited = patterns.some((p) => p.test(stderr));
439
+ if (!isRateLimited) {
440
+ return { rateLimited: false, retryAfterMs: null };
441
+ }
442
+ const retryMatch = /retry.?after:?\s*(\d+)/i.exec(stderr);
443
+ const retryAfterMs = retryMatch?.[1] ? parseInt(retryMatch[1], 10) * 1e3 : null;
444
+ return { rateLimited: true, retryAfterMs };
445
+ },
446
+ getSpawnEnv() {
447
+ return {};
448
+ }
449
+ };
450
+
451
+ // src/integration/external/provider.ts
452
+ async function resolveProvider() {
453
+ const stored = await getAiProvider();
454
+ if (stored) return stored;
455
+ const choice = await getPrompt().select({
456
+ message: `${emoji.donut} Which AI buddy should help with my homework?`,
457
+ choices: [
458
+ { label: "Claude Code", value: "claude" },
459
+ { label: "GitHub Copilot", value: "copilot" }
460
+ ]
461
+ });
462
+ await setAiProvider(choice);
463
+ return choice;
464
+ }
465
+ function providerDisplayName(provider) {
466
+ return provider === "claude" ? "Claude" : "Copilot";
467
+ }
468
+
469
+ // src/integration/ai/providers/registry.ts
470
+ function getProvider(provider) {
471
+ switch (provider) {
472
+ case "claude":
473
+ return claudeAdapter;
474
+ case "copilot":
475
+ return copilotAdapter;
476
+ }
477
+ }
478
+ async function getActiveProvider() {
479
+ const provider = await resolveProvider();
480
+ return getProvider(provider);
481
+ }
482
+
483
+ // src/integration/ai/session/session.ts
484
+ function spawnInteractive(prompt, options, provider) {
485
+ assertSafeCwd(options.cwd);
486
+ const args = prompt ? provider.buildInteractiveArgs(prompt, options.args ?? []) : [...provider.baseArgs, ...options.args ?? []];
487
+ const env = options.env ? { ...process.env, ...options.env } : void 0;
488
+ const result = spawnSync(provider.binary, args, {
489
+ cwd: options.cwd,
490
+ stdio: "inherit",
491
+ env
492
+ });
493
+ if (result.error) {
494
+ return { code: 1, error: `Failed to spawn ${provider.binary} CLI: ${result.error.message}` };
495
+ }
496
+ return { code: result.status ?? 1 };
497
+ }
498
+ async function spawnHeadless(options, provider) {
499
+ assertSafeCwd(options.cwd);
500
+ const p = provider ?? await getActiveProvider();
501
+ return new Promise((resolve, reject) => {
502
+ const allArgs = p.buildHeadlessArgs(options.args ?? []);
503
+ if (options.resumeSessionId) {
504
+ try {
505
+ allArgs.push(...p.buildResumeArgs(options.resumeSessionId));
506
+ } catch {
507
+ reject(new SpawnError("Invalid session ID format", "", 1));
508
+ return;
509
+ }
510
+ }
511
+ const child = spawn(p.binary, allArgs, {
512
+ cwd: options.cwd,
513
+ stdio: ["pipe", "pipe", "pipe"],
514
+ env: options.env ? { ...process.env, ...options.env } : void 0
515
+ });
516
+ const manager = ProcessManager.getInstance();
517
+ try {
518
+ manager.registerChild(child);
519
+ } catch {
520
+ reject(new SpawnError("Cannot spawn during shutdown", "", 1));
521
+ return;
522
+ }
523
+ let detachAbort = null;
524
+ if (options.abortSignal) {
525
+ detachAbort = manager.registerAbort(options.abortSignal, () => {
526
+ try {
527
+ child.kill("SIGTERM");
528
+ } catch {
529
+ }
530
+ });
531
+ }
532
+ const MAX_STDOUT_SIZE = 1e7;
533
+ const MAX_PROMPT_SIZE = 1e6;
534
+ if (options.prompt) {
535
+ if (options.prompt.length > MAX_PROMPT_SIZE) {
536
+ reject(new SpawnError("Prompt exceeds maximum size (1MB)", "", 1));
537
+ return;
538
+ }
539
+ child.stdin.write(options.prompt);
540
+ }
541
+ child.stdin.end();
542
+ let rawStdout = "";
543
+ let stderr = "";
544
+ child.stdout.on("data", (data) => {
545
+ if (rawStdout.length < MAX_STDOUT_SIZE) {
546
+ rawStdout += data.toString();
547
+ }
548
+ });
549
+ child.stderr.on("data", (data) => {
550
+ stderr += data.toString();
551
+ });
552
+ child.on("close", (code) => {
553
+ detachAbort?.();
554
+ void (async () => {
555
+ const exitCode = code ?? 1;
556
+ const { result, sessionId: parsedSessionId, model: parsedModel } = p.parseJsonOutput(rawStdout);
557
+ const sessionId = parsedSessionId ?? await p.extractSessionId?.(options.cwd) ?? null;
558
+ if (exitCode !== 0) {
559
+ reject(
560
+ new SpawnError(
561
+ `${p.displayName} CLI exited with code ${String(exitCode)}: ${stderr}`,
562
+ stderr,
563
+ exitCode,
564
+ sessionId
565
+ )
566
+ );
567
+ } else {
568
+ resolve({ stdout: result, stderr, exitCode: 0, sessionId, model: parsedModel });
569
+ }
570
+ })().catch((err) => {
571
+ reject(new SpawnError(`Unexpected error in close handler: ${String(err)}`, "", 1));
572
+ });
573
+ });
574
+ child.on("error", (err) => {
575
+ detachAbort?.();
576
+ reject(new SpawnError(`Failed to spawn ${p.binary} CLI: ${err.message}`, "", 1));
577
+ });
578
+ });
579
+ }
580
+ var DEFAULT_MAX_RETRIES = 5;
581
+ var BASE_DELAY_MS = 2e3;
582
+ var MAX_DELAY_MS = 12e4;
583
+ var DEFAULT_TOTAL_TIMEOUT_MS = 6e5;
584
+ function sleep(ms) {
585
+ return new Promise((resolve) => setTimeout(resolve, ms));
586
+ }
587
+ function jitter() {
588
+ return Math.floor(Math.random() * 1e3);
589
+ }
590
+ async function spawnWithRetry(options, retryOptions, provider) {
591
+ const p = provider ?? await getActiveProvider();
592
+ const maxRetries = retryOptions?.maxRetries ?? DEFAULT_MAX_RETRIES;
593
+ const totalTimeoutMs = retryOptions?.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT_MS;
594
+ const startTime = Date.now();
595
+ let resumeSessionId = options.resumeSessionId;
596
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
597
+ const elapsed = Date.now() - startTime;
598
+ if (attempt > 0 && elapsed >= totalTimeoutMs) {
599
+ throw new SpawnError(`Total retry timeout exceeded (${String(totalTimeoutMs)}ms)`, "", 1, resumeSessionId);
600
+ }
601
+ if (options.abortSignal?.aborted) {
602
+ throw new SpawnError("Aborted by caller", "", 1, resumeSessionId);
603
+ }
604
+ const r = await wrapAsync(async () => spawnHeadless({ ...options, resumeSessionId }, p), ensureError);
605
+ if (r.ok) return r.value;
606
+ const err = r.error;
607
+ if (!(err instanceof SpawnError) || !err.rateLimited) {
608
+ throw err;
609
+ }
610
+ if (err.sessionId) {
611
+ resumeSessionId = err.sessionId;
612
+ }
613
+ if (attempt >= maxRetries) {
614
+ throw err;
615
+ }
616
+ const delay = Math.min(err.retryAfterMs ?? BASE_DELAY_MS * Math.pow(2, attempt), MAX_DELAY_MS) + jitter();
617
+ retryOptions?.onRetry?.(attempt + 1, delay, err);
618
+ await sleep(delay);
619
+ }
620
+ throw new Error("Max retries exceeded");
621
+ }
622
+
623
+ // src/integration/ai/session/session-adapter.ts
624
+ var ProviderAiSessionAdapter = class {
625
+ provider = null;
626
+ /** Lazily resolve and cache the active provider. */
627
+ async getProvider() {
628
+ this.provider ??= await getActiveProvider();
629
+ return this.provider;
630
+ }
631
+ /** Public eager resolver — required before the sync getters can be used safely. */
632
+ async ensureReady() {
633
+ await this.getProvider();
634
+ }
635
+ async spawnInteractive(prompt, options) {
636
+ const provider = await this.getProvider();
637
+ await withSuspendedTui(() => {
638
+ const result = spawnInteractive(
639
+ prompt,
640
+ {
641
+ cwd: options.cwd,
642
+ args: options.args,
643
+ env: options.env
644
+ },
645
+ provider
646
+ );
647
+ if (result.error) {
648
+ throw new Error(result.error);
649
+ }
650
+ });
651
+ }
652
+ async spawnHeadless(prompt, options) {
653
+ const provider = await this.getProvider();
654
+ const result = await spawnHeadless(
655
+ {
656
+ cwd: options.cwd,
657
+ args: options.args,
658
+ env: options.env,
659
+ prompt,
660
+ resumeSessionId: options.resumeSessionId,
661
+ abortSignal: options.abortSignal
662
+ },
663
+ provider
664
+ );
665
+ return {
666
+ output: result.stdout,
667
+ sessionId: result.sessionId ?? void 0,
668
+ model: result.model ?? void 0
669
+ };
670
+ }
671
+ async spawnWithRetry(prompt, options) {
672
+ const provider = await this.getProvider();
673
+ const result = await spawnWithRetry(
674
+ {
675
+ cwd: options.cwd,
676
+ args: options.args,
677
+ env: options.env,
678
+ prompt,
679
+ resumeSessionId: options.resumeSessionId,
680
+ abortSignal: options.abortSignal
681
+ },
682
+ { maxRetries: options.maxRetries },
683
+ provider
684
+ );
685
+ return {
686
+ output: result.stdout,
687
+ sessionId: result.sessionId ?? void 0,
688
+ model: result.model ?? void 0
689
+ };
690
+ }
691
+ async resumeSession(sessionId, prompt, options) {
692
+ const provider = await this.getProvider();
693
+ const result = await spawnWithRetry(
694
+ {
695
+ cwd: options.cwd,
696
+ args: options.args,
697
+ env: options.env,
698
+ prompt,
699
+ resumeSessionId: sessionId,
700
+ abortSignal: options.abortSignal
701
+ },
702
+ void 0,
703
+ provider
704
+ );
705
+ return {
706
+ output: result.stdout,
707
+ sessionId: result.sessionId ?? void 0,
708
+ model: result.model ?? void 0
709
+ };
710
+ }
711
+ getProviderName() {
712
+ if (!this.provider) {
713
+ throw new Error("Provider not yet resolved. Call an async method first.");
714
+ }
715
+ return this.provider.name;
716
+ }
717
+ getProviderDisplayName() {
718
+ if (!this.provider) {
719
+ throw new Error("Provider not yet resolved. Call an async method first.");
720
+ }
721
+ return this.provider.displayName;
722
+ }
723
+ getSpawnEnv() {
724
+ if (!this.provider) {
725
+ throw new Error("Provider not yet resolved. Call an async method first.");
726
+ }
727
+ return this.provider.getSpawnEnv();
728
+ }
729
+ };
730
+
731
+ // src/integration/signals/parser.ts
732
+ var SIGNAL_PATTERNS = {
733
+ progress: /<progress>([\s\S]*?)<\/progress>/g,
734
+ progressWithFiles: /<progress>([\s\S]*?)<\/progress>/,
735
+ evaluation_passed: /<evaluation-passed>/,
736
+ evaluation_failed: /<evaluation-failed>([\s\S]*?)<\/evaluation-failed>/,
737
+ task_verified: /<task-verified>([\s\S]*?)<\/task-verified>/,
738
+ task_complete: /<task-complete>/,
739
+ task_blocked: /<task-blocked>([\s\S]*?)<\/task-blocked>/,
740
+ note: /<note>([\s\S]*?)<\/note>/g,
741
+ check_script: /<check-script>([\s\S]*?)<\/check-script>/,
742
+ agents_md: /<agents-md>([\s\S]*?)<\/agents-md>/
743
+ };
744
+ var DIMENSION_LINE = /\*\*([A-Za-z][A-Za-z0-9]{2,29})\*\*\s*:\s*(PASS|FAIL)\s*(?:—|-)\s*(.+)/gi;
745
+ function parseDimensionScores(output) {
746
+ const scores = [];
747
+ const seen = /* @__PURE__ */ new Set();
748
+ DIMENSION_LINE.lastIndex = 0;
749
+ let match;
750
+ while ((match = DIMENSION_LINE.exec(output)) !== null) {
751
+ const rawName = match[1];
752
+ const verdict = match[2];
753
+ const finding = match[3];
754
+ if (!rawName || !verdict || !finding) continue;
755
+ const name = rawName.toLowerCase();
756
+ if (seen.has(name)) continue;
757
+ seen.add(name);
758
+ scores.push({
759
+ dimension: name,
760
+ passed: verdict.toUpperCase() === "PASS",
761
+ finding: finding.trim()
762
+ });
763
+ }
764
+ return scores;
765
+ }
766
+ var DANGEROUS_COMMAND_PATTERNS = [
767
+ /\|\s*(ba)?sh\b/,
768
+ /\bcurl\b[^|;&\n]*\|/,
769
+ /\bwget\b[^|;&\n]*(-O-|--output-document=-)[^|;&\n]*\|/,
770
+ /\beval\b/,
771
+ /\brm\s+-[rf]+\b/
772
+ ];
773
+ function isDangerousCommand(command) {
774
+ return DANGEROUS_COMMAND_PATTERNS.some((re) => re.test(command));
775
+ }
776
+ var SignalParser = class {
777
+ parseSignals(output) {
778
+ const signals = [];
779
+ const timestamp = /* @__PURE__ */ new Date();
780
+ let progressMatch;
781
+ while ((progressMatch = SIGNAL_PATTERNS.progress.exec(output)) !== null) {
782
+ const summary = progressMatch[1]?.trim();
783
+ if (summary) {
784
+ const progressSignal = {
785
+ type: "progress",
786
+ summary,
787
+ // Note: Phase 1 doesn't parse files attribute; added in Phase 2+
788
+ timestamp
789
+ };
790
+ signals.push(progressSignal);
791
+ }
792
+ }
793
+ if (output.includes("<evaluation-passed>")) {
794
+ const dimensions = parseDimensionScores(output);
795
+ const evaluationSignal = {
796
+ type: "evaluation",
797
+ status: "passed",
798
+ dimensions,
799
+ timestamp
800
+ };
801
+ signals.push(evaluationSignal);
802
+ } else {
803
+ const failedMatch = SIGNAL_PATTERNS.evaluation_failed.exec(output);
804
+ if (failedMatch?.[1]) {
805
+ const critique = failedMatch[1].trim();
806
+ const dimensions = parseDimensionScores(output);
807
+ const evaluationSignal = {
808
+ type: "evaluation",
809
+ status: dimensions.length > 0 ? "failed" : "malformed",
810
+ dimensions,
811
+ critique: dimensions.length > 0 ? critique : void 0,
812
+ timestamp
813
+ };
814
+ signals.push(evaluationSignal);
815
+ } else if (parseDimensionScores(output).length > 0) {
816
+ const dimensions = parseDimensionScores(output);
817
+ const evaluationSignal = {
818
+ type: "evaluation",
819
+ status: "failed",
820
+ dimensions,
821
+ timestamp
822
+ };
823
+ signals.push(evaluationSignal);
824
+ }
825
+ }
826
+ const taskVerifiedMatch = SIGNAL_PATTERNS.task_verified.exec(output);
827
+ if (taskVerifiedMatch?.[1]) {
828
+ const verificationOutput = taskVerifiedMatch[1].trim();
829
+ const verifiedSignal = {
830
+ type: "task-verified",
831
+ output: verificationOutput,
832
+ timestamp
833
+ };
834
+ signals.push(verifiedSignal);
835
+ }
836
+ if (output.includes("<task-complete>")) {
837
+ const completeSignal = {
838
+ type: "task-complete",
839
+ timestamp
840
+ };
841
+ signals.push(completeSignal);
842
+ }
843
+ const taskBlockedMatch = SIGNAL_PATTERNS.task_blocked.exec(output);
844
+ if (taskBlockedMatch?.[1]) {
845
+ const reason = taskBlockedMatch[1].trim();
846
+ const blockedSignal = {
847
+ type: "task-blocked",
848
+ reason,
849
+ timestamp
850
+ };
851
+ signals.push(blockedSignal);
852
+ }
853
+ let noteMatch;
854
+ while ((noteMatch = SIGNAL_PATTERNS.note.exec(output)) !== null) {
855
+ const text = noteMatch[1]?.trim();
856
+ if (text) {
857
+ const noteSignal = {
858
+ type: "note",
859
+ text,
860
+ timestamp
861
+ };
862
+ signals.push(noteSignal);
863
+ }
864
+ }
865
+ const checkScriptMatch = SIGNAL_PATTERNS.check_script.exec(output);
866
+ if (checkScriptMatch?.[1]) {
867
+ const command = checkScriptMatch[1].trim();
868
+ if (command.length > 0 && !isDangerousCommand(command)) {
869
+ const checkScriptSignal = {
870
+ type: "check-script-discovery",
871
+ command,
872
+ timestamp
873
+ };
874
+ signals.push(checkScriptSignal);
875
+ }
876
+ }
877
+ const agentsMdMatch = SIGNAL_PATTERNS.agents_md.exec(output);
878
+ if (agentsMdMatch?.[1]) {
879
+ const content = agentsMdMatch[1].trim();
880
+ if (content.length > 0) {
881
+ const agentsMdSignal = {
882
+ type: "agents-md-proposal",
883
+ content,
884
+ timestamp
885
+ };
886
+ signals.push(agentsMdSignal);
887
+ }
888
+ }
889
+ return signals;
890
+ }
891
+ };
892
+
893
+ // src/integration/ai/prompts/loader.ts
894
+ import { existsSync, readFileSync } from "fs";
895
+ import { dirname, join as join2 } from "path";
896
+ import { fileURLToPath } from "url";
897
+ var __dirname = dirname(fileURLToPath(import.meta.url));
898
+ function getPromptDir() {
899
+ const bundled = join2(__dirname, "prompts");
900
+ if (existsSync(bundled)) return bundled;
901
+ return __dirname;
902
+ }
903
+ var promptDir = getPromptDir();
904
+ function loadTemplate(name) {
905
+ return readFileSync(join2(promptDir, `${name}.md`), "utf-8");
906
+ }
907
+ function loadPartial(name) {
908
+ return loadTemplate(name).replace(/\s+$/, "");
909
+ }
910
+ var UNREPLACED_TOKEN_RE = /\{\{[A-Z_]+\}\}/g;
911
+ function composePrompt(template, substitutions) {
912
+ let result = template;
913
+ for (const [key, value] of Object.entries(substitutions)) {
914
+ result = result.replaceAll(`{{${key}}}`, value);
915
+ }
916
+ const remaining = result.match(UNREPLACED_TOKEN_RE);
917
+ if (remaining) {
918
+ throw new Error(`composePrompt: unreplaced placeholders: ${[...new Set(remaining)].join(", ")}`);
919
+ }
920
+ return result;
921
+ }
922
+ var CHECK_GATE_EXAMPLE = "Run the project's check gate \u2014 all pass (omit this step when the project has no check script)";
923
+ function buildPlanCommon(projectToolingSection) {
924
+ return composePrompt(loadPartial("plan-common"), {
925
+ PLAN_COMMON_EXAMPLES: loadPartial("plan-common-examples"),
926
+ PROJECT_TOOLING: projectToolingSection,
927
+ CHECK_GATE_EXAMPLE
928
+ });
929
+ }
930
+ function buildPlannerBase(projectToolingSection) {
931
+ return {
932
+ HARNESS_CONTEXT: loadPartial("harness-context"),
933
+ COMMON: buildPlanCommon(projectToolingSection),
934
+ VALIDATION: loadPartial("validation-checklist"),
935
+ SIGNALS: loadPartial("signals-planning"),
936
+ CHECK_GATE_EXAMPLE
937
+ };
938
+ }
939
+ function buildInteractivePrompt(context, outputFile, schema, projectToolingSection) {
940
+ return composePrompt(loadTemplate("plan-interactive"), {
941
+ ...buildPlannerBase(projectToolingSection),
942
+ CONTEXT: context,
943
+ OUTPUT_FILE: outputFile,
944
+ SCHEMA: schema
945
+ });
946
+ }
947
+ function buildAutoPrompt(context, schema, projectToolingSection) {
948
+ return composePrompt(loadTemplate("plan-auto"), {
949
+ ...buildPlannerBase(projectToolingSection),
950
+ CONTEXT: context,
951
+ SCHEMA: schema
952
+ });
953
+ }
954
+ function buildTaskExecutionPrompt(progressFilePath, noCommit, contextFileName, projectToolingSection = "") {
955
+ let template = loadTemplate("task-execution");
956
+ if (noCommit) {
957
+ template = template.replace(/^[ \t]*\{\{COMMIT_STEP\}\}\n/m, "\n");
958
+ template = template.replace(/^[ \t]*\{\{COMMIT_CONSTRAINT\}\}\n/m, "");
959
+ }
960
+ const commitStep = noCommit ? "" : " - **Before continuing:** Create a git commit with a descriptive message for the changes made.";
961
+ const commitConstraint = noCommit ? "" : "- **Must commit** \u2014 Create a git commit before signaling completion.";
962
+ return composePrompt(template, {
963
+ HARNESS_CONTEXT: loadPartial("harness-context"),
964
+ SIGNALS: loadPartial("signals-task"),
965
+ PROGRESS_FILE: progressFilePath,
966
+ COMMIT_STEP: commitStep,
967
+ COMMIT_CONSTRAINT: commitConstraint,
968
+ CONTEXT_FILE: contextFileName,
969
+ PROJECT_TOOLING: projectToolingSection
970
+ });
971
+ }
972
+ function buildTicketRefinePrompt(ticketContent, outputFile, schema, issueContext = "") {
973
+ const template = loadTemplate("ticket-refine");
974
+ const issueContextSection = issueContext ? `<context>
975
+
976
+ ${issueContext}
977
+
978
+ </context>` : "";
979
+ return composePrompt(template, {
980
+ TICKET: ticketContent,
981
+ OUTPUT_FILE: outputFile,
982
+ SCHEMA: schema,
983
+ ISSUE_CONTEXT: issueContextSection
984
+ });
985
+ }
986
+ function buildIdeatePrompt(ideaTitle, ideaDescription, projectName, repositories, outputFile, schema, projectToolingSection) {
987
+ return composePrompt(loadTemplate("ideate"), {
988
+ ...buildPlannerBase(projectToolingSection),
989
+ IDEA_TITLE: ideaTitle,
990
+ IDEA_DESCRIPTION: ideaDescription,
991
+ PROJECT_NAME: projectName,
992
+ REPOSITORIES: repositories,
993
+ OUTPUT_FILE: outputFile,
994
+ SCHEMA: schema
995
+ });
996
+ }
997
+ function buildIdeateAutoPrompt(ideaTitle, ideaDescription, projectName, repositories, schema, projectToolingSection) {
998
+ return composePrompt(loadTemplate("ideate-auto"), {
999
+ ...buildPlannerBase(projectToolingSection),
1000
+ IDEA_TITLE: ideaTitle,
1001
+ IDEA_DESCRIPTION: ideaDescription,
1002
+ PROJECT_NAME: projectName,
1003
+ REPOSITORIES: repositories,
1004
+ SCHEMA: schema
1005
+ });
1006
+ }
1007
+ function renderExtraDimensions(extras) {
1008
+ if (extras.length === 0) {
1009
+ return { section: "", passBar: "", assessment: "" };
1010
+ }
1011
+ const section = extras.map(
1012
+ (name) => `
1013
+ <dimension name="${name}" floor="false">
1014
+ Additional task-specific dimension flagged by the planner. Apply judgment to whether the implementation satisfies this dimension given the task's verification criteria and steps.
1015
+ </dimension>
1016
+ `
1017
+ ).join("");
1018
+ const passBar = extras.map((name) => `
1019
+ - **${name}**: Task-specific dimension flagged by the planner`).join("");
1020
+ return {
1021
+ section,
1022
+ passBar,
1023
+ assessment: extras.map((name) => `
1024
+ **${name}**: PASS/FAIL \u2014 [one-line finding]`).join("")
1025
+ };
1026
+ }
1027
+ function buildEvaluatorPrompt(ctx) {
1028
+ const template = loadTemplate("task-evaluation");
1029
+ const descriptionSection = ctx.taskDescription ? `
1030
+ **Description:** ${ctx.taskDescription}` : "";
1031
+ const stepsSection = ctx.taskSteps.length > 0 ? `
1032
+ **Implementation Steps:**
1033
+ ${ctx.taskSteps.map((s) => `- ${s}`).join("\n")}` : "";
1034
+ const criteriaSection = ctx.verificationCriteria.length > 0 ? `
1035
+ **Verification Criteria:**
1036
+ ${ctx.verificationCriteria.map((c) => `- ${c}`).join("\n")}` : "";
1037
+ const checkSection = ctx.checkScriptSection ? `
1038
+
1039
+ ${ctx.checkScriptSection}` : "";
1040
+ const extras = renderExtraDimensions(ctx.extraDimensions);
1041
+ const extraAssessmentPass = extras.assessment.replace(/PASS\/FAIL/g, "PASS");
1042
+ return composePrompt(template, {
1043
+ HARNESS_CONTEXT: loadPartial("harness-context"),
1044
+ SIGNALS: loadPartial("signals-evaluation"),
1045
+ TASK_NAME: ctx.taskName,
1046
+ TASK_DESCRIPTION_SECTION: descriptionSection,
1047
+ TASK_STEPS_SECTION: stepsSection,
1048
+ VERIFICATION_CRITERIA_SECTION: criteriaSection,
1049
+ PROJECT_PATH: ctx.projectPath,
1050
+ CHECK_SCRIPT_SECTION: checkSection,
1051
+ PROJECT_TOOLING: ctx.projectToolingSection,
1052
+ EXTRA_DIMENSIONS_SECTION: extras.section,
1053
+ EXTRA_DIMENSIONS_PASS_BAR: extras.passBar,
1054
+ EXTRA_DIMENSIONS_ASSESSMENT_PASS: extraAssessmentPass,
1055
+ EXTRA_DIMENSIONS_ASSESSMENT_MIXED: extras.assessment
1056
+ });
1057
+ }
1058
+ function buildSprintFeedbackPrompt(sprintName, completedTasks, feedback, branch) {
1059
+ const template = loadTemplate("sprint-feedback");
1060
+ const branchSection = branch ? `
1061
+ **Branch:** ${branch}
1062
+ ` : "";
1063
+ return composePrompt(template, {
1064
+ HARNESS_CONTEXT: loadPartial("harness-context"),
1065
+ SIGNALS: loadPartial("signals-task"),
1066
+ SPRINT_NAME: sprintName,
1067
+ BRANCH_SECTION: branchSection,
1068
+ COMPLETED_TASKS: completedTasks,
1069
+ FEEDBACK: feedback
1070
+ });
1071
+ }
1072
+ function buildCheckScriptDiscoverPrompt(repoPath) {
1073
+ return composePrompt(loadTemplate("check-script-discover"), {
1074
+ REPO_PATH: repoPath
1075
+ });
1076
+ }
1077
+ function buildRepoOnboardPrompt(ctx) {
1078
+ const existingSection = ctx.existingAgentsMd ? `
1079
+ **Existing project context file:**
1080
+
1081
+ \`\`\`
1082
+ ${ctx.existingAgentsMd}
1083
+ \`\`\`
1084
+ ` : "";
1085
+ return composePrompt(loadTemplate("repo-onboard"), {
1086
+ REPO_PATH: ctx.repoPath,
1087
+ MODE: ctx.mode,
1088
+ EXISTING_AGENTS_MD: existingSection,
1089
+ PROJECT_TYPE: ctx.projectType || "unknown",
1090
+ CHECK_SCRIPT_SUGGESTION: ctx.checkScriptSuggestion,
1091
+ FILE_NAME: ctx.fileName
1092
+ });
1093
+ }
1094
+
1095
+ export {
1096
+ buildInteractivePrompt,
1097
+ buildAutoPrompt,
1098
+ buildTaskExecutionPrompt,
1099
+ buildTicketRefinePrompt,
1100
+ buildIdeatePrompt,
1101
+ buildIdeateAutoPrompt,
1102
+ buildEvaluatorPrompt,
1103
+ buildSprintFeedbackPrompt,
1104
+ buildCheckScriptDiscoverPrompt,
1105
+ buildRepoOnboardPrompt,
1106
+ processLifecycleAdapter,
1107
+ resolveProvider,
1108
+ providerDisplayName,
1109
+ getActiveProvider,
1110
+ spawnInteractive,
1111
+ enterAltScreen,
1112
+ exitAltScreen,
1113
+ registerTuiInstance,
1114
+ withSuspendedTui,
1115
+ ProviderAiSessionAdapter,
1116
+ SignalParser
1117
+ };