ralph-flow-pi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +428 -0
  3. package/dist/cli.d.ts +9 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +56 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/prompts.d.ts +25 -0
  8. package/dist/commands/prompts.d.ts.map +1 -0
  9. package/dist/commands/prompts.js +249 -0
  10. package/dist/commands/prompts.js.map +1 -0
  11. package/dist/commands/tools.d.ts +47 -0
  12. package/dist/commands/tools.d.ts.map +1 -0
  13. package/dist/commands/tools.js +633 -0
  14. package/dist/commands/tools.js.map +1 -0
  15. package/dist/engine/check-bash.d.ts +121 -0
  16. package/dist/engine/check-bash.d.ts.map +1 -0
  17. package/dist/engine/check-bash.js +373 -0
  18. package/dist/engine/check-bash.js.map +1 -0
  19. package/dist/engine/check.d.ts +47 -0
  20. package/dist/engine/check.d.ts.map +1 -0
  21. package/dist/engine/check.js +298 -0
  22. package/dist/engine/check.js.map +1 -0
  23. package/dist/engine/core.d.ts +153 -0
  24. package/dist/engine/core.d.ts.map +1 -0
  25. package/dist/engine/core.js +1984 -0
  26. package/dist/engine/core.js.map +1 -0
  27. package/dist/engine/lock.d.ts +27 -0
  28. package/dist/engine/lock.d.ts.map +1 -0
  29. package/dist/engine/lock.js +121 -0
  30. package/dist/engine/lock.js.map +1 -0
  31. package/dist/engine/runner.d.ts +108 -0
  32. package/dist/engine/runner.d.ts.map +1 -0
  33. package/dist/engine/runner.js +510 -0
  34. package/dist/engine/runner.js.map +1 -0
  35. package/dist/engine/skills.d.ts +53 -0
  36. package/dist/engine/skills.d.ts.map +1 -0
  37. package/dist/engine/skills.js +109 -0
  38. package/dist/engine/skills.js.map +1 -0
  39. package/dist/engine/step-tools.d.ts +22 -0
  40. package/dist/engine/step-tools.d.ts.map +1 -0
  41. package/dist/engine/step-tools.js +45 -0
  42. package/dist/engine/step-tools.js.map +1 -0
  43. package/dist/engine/types.d.ts +136 -0
  44. package/dist/engine/types.d.ts.map +1 -0
  45. package/dist/engine/types.js +20 -0
  46. package/dist/engine/types.js.map +1 -0
  47. package/dist/headless.d.ts +57 -0
  48. package/dist/headless.d.ts.map +1 -0
  49. package/dist/headless.js +318 -0
  50. package/dist/headless.js.map +1 -0
  51. package/dist/pi/adapter.d.ts +135 -0
  52. package/dist/pi/adapter.d.ts.map +1 -0
  53. package/dist/pi/adapter.js +231 -0
  54. package/dist/pi/adapter.js.map +1 -0
  55. package/dist/pi/interactive.d.ts +28 -0
  56. package/dist/pi/interactive.d.ts.map +1 -0
  57. package/dist/pi/interactive.js +58 -0
  58. package/dist/pi/interactive.js.map +1 -0
  59. package/dist/pi/tui.d.ts +12 -0
  60. package/dist/pi/tui.d.ts.map +1 -0
  61. package/dist/pi/tui.js +12 -0
  62. package/dist/pi/tui.js.map +1 -0
  63. package/dist/tui/app.d.ts +25 -0
  64. package/dist/tui/app.d.ts.map +1 -0
  65. package/dist/tui/app.js +47 -0
  66. package/dist/tui/app.js.map +1 -0
  67. package/dist/tui/embed.d.ts +42 -0
  68. package/dist/tui/embed.d.ts.map +1 -0
  69. package/dist/tui/embed.js +38 -0
  70. package/dist/tui/embed.js.map +1 -0
  71. package/dist/tui/extension.d.ts +88 -0
  72. package/dist/tui/extension.d.ts.map +1 -0
  73. package/dist/tui/extension.js +114 -0
  74. package/dist/tui/extension.js.map +1 -0
  75. package/dist/tui/history-editor.d.ts +38 -0
  76. package/dist/tui/history-editor.d.ts.map +1 -0
  77. package/dist/tui/history-editor.js +55 -0
  78. package/dist/tui/history-editor.js.map +1 -0
  79. package/dist/tui/launcher.d.ts +24 -0
  80. package/dist/tui/launcher.d.ts.map +1 -0
  81. package/dist/tui/launcher.js +97 -0
  82. package/dist/tui/launcher.js.map +1 -0
  83. package/dist/tui/render.d.ts +87 -0
  84. package/dist/tui/render.d.ts.map +1 -0
  85. package/dist/tui/render.js +266 -0
  86. package/dist/tui/render.js.map +1 -0
  87. package/dist/tui/run-app.d.ts +49 -0
  88. package/dist/tui/run-app.d.ts.map +1 -0
  89. package/dist/tui/run-app.js +317 -0
  90. package/dist/tui/run-app.js.map +1 -0
  91. package/dist/tui/run-model.d.ts +162 -0
  92. package/dist/tui/run-model.d.ts.map +1 -0
  93. package/dist/tui/run-model.js +280 -0
  94. package/dist/tui/run-model.js.map +1 -0
  95. package/dist/tui/run-view.d.ts +71 -0
  96. package/dist/tui/run-view.d.ts.map +1 -0
  97. package/dist/tui/run-view.js +167 -0
  98. package/dist/tui/run-view.js.map +1 -0
  99. package/dist/tui/welcome-header.d.ts +40 -0
  100. package/dist/tui/welcome-header.d.ts.map +1 -0
  101. package/dist/tui/welcome-header.js +90 -0
  102. package/dist/tui/welcome-header.js.map +1 -0
  103. package/package.json +55 -0
  104. package/skills/c-to-rust-audit/SKILL.md +67 -0
  105. package/skills/c-to-rust-implement/SKILL.md +151 -0
  106. package/skills/c-to-rust-implement/references/c-to-rust-patterns.md +86 -0
  107. package/skills/c-to-rust-implement/references/conditional-compilation.md +47 -0
  108. package/skills/c-to-rust-implement/references/crate-reference.md +15 -0
  109. package/skills/c-to-rust-implement/references/error-strategies.md +80 -0
  110. package/skills/c-to-rust-implement/references/inline-asm.md +37 -0
  111. package/skills/c-to-rust-plan/SKILL.md +166 -0
  112. package/skills/c-to-rust-plan/references/detection-commands.md +66 -0
  113. package/skills/c-to-rust-test-gen/SKILL.md +130 -0
  114. package/skills/c-to-rust-test-gen/references/proptest-patterns.md +81 -0
  115. package/skills/c-to-rust-test-gen/references/test-porting.md +56 -0
  116. package/skills/c-to-rust-validate/SKILL.md +121 -0
  117. package/skills/everything2rust-audit/SKILL.md +69 -0
  118. package/skills/everything2rust-design/SKILL.md +121 -0
  119. package/skills/everything2rust-design/references/domain-playbooks.md +68 -0
  120. package/skills/everything2rust-design/references/paradigm-map.md +99 -0
  121. package/skills/everything2rust-implement/SKILL.md +101 -0
  122. package/skills/everything2rust-spec/SKILL.md +86 -0
  123. package/skills/everything2rust-spec/references/oracle-strategies.md +96 -0
  124. package/skills/everything2rust-survey/SKILL.md +99 -0
  125. package/skills/everything2rust-test-gen/SKILL.md +68 -0
  126. package/skills/everything2rust-test-gen/references/harness-patterns.md +186 -0
  127. package/skills/everything2rust-validate/SKILL.md +85 -0
  128. package/workflows/c-to-rust.yaml +202 -0
  129. package/workflows/everything2rust.yaml +259 -0
  130. package/workflows/loop.yaml +68 -0
  131. package/workflows/spec.yaml +183 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Pure rendering: RunModel → terminal lines.
3
+ *
4
+ * No pi-tui, no side effects, no I/O. Given a model and a width it returns the
5
+ * exact lines the run view shows, so the entire look of the product is unit-
6
+ * testable without a terminal. run-view.ts is a thin pi-tui wrapper that pipes
7
+ * these lines to the screen and calls back in on input.
8
+ *
9
+ * The layout is a growing stream on top with a persistent status block pinned at
10
+ * the bottom (pi-tui's TUI keeps the tail visible, so "bottom" is always on
11
+ * screen while stream history scrolls up — the same shape as a chat's fixed
12
+ * input bar). This is what a chat transcript could never give: a live pipeline
13
+ * you can see all of, with each step's real work streaming in place.
14
+ */
15
+ import { isTerminal, needsUser, } from "./run-model.js";
16
+ import { visibleWidth, wrapTextWithAnsi, truncateToWidth } from "../pi/tui.js";
17
+ // ─── minimal ANSI (kept local so this file stays pure and dependency-free) ────
18
+ const ESC = "\x1b[";
19
+ const codes = {
20
+ reset: `${ESC}0m`,
21
+ dim: `${ESC}2m`,
22
+ bold: `${ESC}1m`,
23
+ red: `${ESC}31m`,
24
+ green: `${ESC}32m`,
25
+ yellow: `${ESC}33m`,
26
+ blue: `${ESC}34m`,
27
+ magenta: `${ESC}35m`,
28
+ cyan: `${ESC}36m`,
29
+ gray: `${ESC}90m`,
30
+ };
31
+ const c = (code, s) => `${code}${s}${codes.reset}`;
32
+ export const dim = (s) => c(codes.dim, s);
33
+ export const bold = (s) => c(codes.bold, s);
34
+ export const green = (s) => c(codes.green, s);
35
+ export const red = (s) => c(codes.red, s);
36
+ export const yellow = (s) => c(codes.yellow, s);
37
+ export const cyan = (s) => c(codes.cyan, s);
38
+ export const gray = (s) => c(codes.gray, s);
39
+ export const magenta = (s) => c(codes.magenta, s);
40
+ /**
41
+ * Display width, counting CJK/emoji as 2 columns (delegates to pi-tui so it
42
+ * agrees exactly with the width pi-tui's renderer enforces — a mismatch makes
43
+ * the TUI throw "line exceeds terminal width").
44
+ */
45
+ export function visibleLength(s) {
46
+ return visibleWidth(s);
47
+ }
48
+ /**
49
+ * Wrap a logical line to `width` display columns, ANSI- and wide-char-aware.
50
+ * Uses pi-tui's own wrapper, so every line we emit is guaranteed to fit.
51
+ */
52
+ export function wrap(text, width) {
53
+ if (width <= 0)
54
+ return [text];
55
+ const out = [];
56
+ for (const raw of text.split("\n")) {
57
+ const wrapped = wrapTextWithAnsi(raw, width);
58
+ if (wrapped.length === 0)
59
+ out.push("");
60
+ else
61
+ for (const l of wrapped)
62
+ out.push(l);
63
+ }
64
+ return out;
65
+ }
66
+ // ─── step list (the compact pipeline row that stays visible) ──────────────────
67
+ const STEP_ICON = {
68
+ pending: gray("○"),
69
+ do: cyan("▶"),
70
+ check: yellow("🔍"),
71
+ passed: green("✓"),
72
+ failed: red("✗"),
73
+ gate: magenta("📋"),
74
+ done: green("✓"),
75
+ };
76
+ function stepIcon(status) {
77
+ return STEP_ICON[status] ?? gray("○");
78
+ }
79
+ /**
80
+ * Pipeline strip: an icon-only progress row, then a dedicated line naming
81
+ * the active step — number, description, DO/CHECK, elapsed. Two lines, not
82
+ * one, on purpose: cramming "步骤 3/7:技术方案设计 DO 45s" onto the same
83
+ * line as the icon row (the original design) made a user report they could
84
+ * only tell "some step is running" from a glance — the icons read fine as a
85
+ * progress bar, but the one piece of text answering "which step, doing
86
+ * what, DO or CHECK" was easy to miss appended after seven icons. Giving it
87
+ * its own line, led with the step's ordinal position, fixes that without
88
+ * adding new information — everything here was already computed, just
89
+ * under-emphasized. `now` defaults to the real clock but is an explicit
90
+ * param so tests stay deterministic — this is the only place render.ts
91
+ * touches wall-clock time at all.
92
+ */
93
+ export function renderStepStrip(m, width, now = Date.now()) {
94
+ const icons = m.steps.map((s) => (s.id === m.activeStepId ? bold(stepIcon(s.status)) : stepIcon(s.status))).join(" ");
95
+ const active = m.steps.find((s) => s.id === m.activeStepId);
96
+ const out = wrap(icons, width);
97
+ if (active)
98
+ out.push(...wrap(activeLabel(m, active, now), width));
99
+ return out;
100
+ }
101
+ function activeLabel(m, step, now) {
102
+ const ordinal = m.steps.findIndex((s) => s.id === step.id) + 1;
103
+ const stepNum = bold(`步骤 ${ordinal}/${m.steps.length}`);
104
+ const phaseTag = m.activePhase === "do" ? cyan("DO") : m.activePhase === "check" ? yellow("CHECK") : "";
105
+ const elapsed = m.activePhase && m.phaseStartedAt ? dim(` ${formatElapsed(now - m.phaseStartedAt)}`) : "";
106
+ return `${stepNum}${dim(":")}${step.desc}${phaseTag ? " " + phaseTag : ""}${elapsed}`;
107
+ }
108
+ /**
109
+ * "3m05s" / "42s" — the only way to tell "still thinking" from "stuck" during
110
+ * a long silent stretch (no reasoning/tool events at all) is a ticking clock,
111
+ * not the stream itself.
112
+ */
113
+ function formatElapsed(ms) {
114
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
115
+ const minutes = Math.floor(totalSeconds / 60);
116
+ const seconds = totalSeconds % 60;
117
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
118
+ }
119
+ // ─── the stream (the active step's live work) ─────────────────────────────────
120
+ /** Render one block. Reasoning is dimmed; tools show their key arg + a few result lines. */
121
+ export function renderBlock(b, width) {
122
+ switch (b.kind) {
123
+ case "phase":
124
+ return [dim("─".repeat(Math.min(width, 40))), ...wrap(bold(b.phase === "do" ? `▶ DO${b.attempt > 1 ? ` · 第 ${b.attempt} 次` : ""}` : `🔍 CHECK(独立只读验证)`), width)];
125
+ case "reasoning": {
126
+ const lines = wrap(b.text.trim(), width);
127
+ return lines.map((l) => dim(l));
128
+ }
129
+ case "text":
130
+ return wrap(b.text.trim(), width);
131
+ case "tool": {
132
+ const lines = wrap(toolHead(b), width);
133
+ if (b.result && b.result.trim()) {
134
+ const resultLines = wrap(b.result.trim(), Math.max(1, width - 2)).slice(0, 12);
135
+ for (const rl of resultLines)
136
+ lines.push(" " + dim(rl));
137
+ }
138
+ return lines;
139
+ }
140
+ case "verdict": {
141
+ if (b.infra)
142
+ return [yellow(`⚠ 验证未能运行:${b.reason}`)];
143
+ const head = b.passed ? green(`✓ 通过`) : red(`✗ 未通过`);
144
+ return wrap(`${head} ${dim("— " + b.reason)}`, width);
145
+ }
146
+ case "notice":
147
+ return wrap(b.text, width);
148
+ case "user":
149
+ return wrap(bold(cyan("› 你:")) + " " + b.text, width);
150
+ }
151
+ }
152
+ function toolHead(b) {
153
+ const mark = b.status === "running" ? cyan("▸") : b.status === "error" ? red("✗") : green("▸");
154
+ const arg = b.arg ? " " + dim(b.arg) : "";
155
+ return `${mark} ${b.name}${arg}`;
156
+ }
157
+ /** The active step's stream as lines. */
158
+ export function renderStream(m, width) {
159
+ const blocks = m.activeStepId ? m.streams.get(m.activeStepId) ?? [] : [];
160
+ const out = [];
161
+ for (const b of blocks) {
162
+ for (const line of renderBlock(b, width))
163
+ out.push(line);
164
+ }
165
+ return out;
166
+ }
167
+ // ─── the persistent status block (pinned at the bottom) ───────────────────────
168
+ const STATUS_HEAD = {
169
+ running: cyan("▶ 运行中"),
170
+ gate: magenta("📋 等待审查"),
171
+ paused: yellow("⏸ 已暂停"),
172
+ stalled: yellow("⚠ 已停止驱动"),
173
+ completed: green("✓ 已完成"),
174
+ cancelled: gray("已取消"),
175
+ };
176
+ /** Header + step strip + action prompt + hints — always visible at the bottom. */
177
+ export function renderStatus(m, width, now = Date.now()) {
178
+ const out = [];
179
+ out.push(dim("─".repeat(width)));
180
+ // Title line: workflow · task.
181
+ const title = `${bold(m.workflowName)}${dim(" · ")}${truncate(m.task, Math.max(10, width - visibleLength(m.workflowName) - 4))}`;
182
+ out.push(...wrap(title, width));
183
+ // Pipeline strip (icons, then "步骤 N/Total:desc DO/CHECK elapsed" — the
184
+ // step counter lives there now, right next to what it's counting; restating
185
+ // it here would just be the same number twice).
186
+ out.push(...renderStepStrip(m, width, now));
187
+ // Run status (running / gate / paused / stalled / completed / cancelled).
188
+ out.push(...wrap(STATUS_HEAD[m.status], width));
189
+ // Action prompt when the user is needed.
190
+ for (const line of renderAction(m, width))
191
+ out.push(line);
192
+ // Key hints.
193
+ out.push(...wrap(dim(hintLine(m)), width));
194
+ return out;
195
+ }
196
+ /** The actionable block for gate / pause / stall / completion. */
197
+ export function renderAction(m, width) {
198
+ if (m.status === "gate") {
199
+ return [
200
+ ...wrap(magenta("📋 ") + m.statusDetail, width),
201
+ ...wrap(`直接打字提修改意见,或输入 ${bold("/ralphflow-continue")} 通过、${bold("/ralphflow-cancel")} 取消`, width),
202
+ ];
203
+ }
204
+ if (m.status === "paused" || m.status === "stalled") {
205
+ return [
206
+ ...wrap(yellow("⏸ ") + m.statusDetail, width),
207
+ ...wrap(`可以先打字留一句补充说明,再输入 ${bold("/ralphflow-continue")} 继续、${bold("/ralphflow-cancel")} 取消`, width),
208
+ ];
209
+ }
210
+ if (m.status === "completed") {
211
+ const rp = m.reportPath ? dim(`报告:${m.reportPath}`) : "";
212
+ return wrap(green("✓ 工作流完成。") + (rp ? " " + rp : ""), width);
213
+ }
214
+ return [];
215
+ }
216
+ function hintLine(m) {
217
+ if (isTerminal(m))
218
+ return "Esc 退出 · ↑↓ 滚动";
219
+ if (m.activePhase === "check")
220
+ return "🔍 独立验证中,不可插话 · ↑↓ 滚动 · Esc 退出";
221
+ if (needsUser(m))
222
+ return "↑↓ 滚动 · Esc 退出(输入框为空时)";
223
+ return "随时打字插话,模型会接着聊 · ↑↓ 滚动 · Esc 退出(输入框为空时)";
224
+ }
225
+ // ─── whole screen (stream on top, status pinned below) ────────────────────────
226
+ /**
227
+ * The complete document. pi-tui's `TUI` has no real scrollable viewport —
228
+ * there's no API for "pin this footer, scroll that region above it" (checked:
229
+ * `TUI extends Container`'s public surface has nothing scroll-related; the
230
+ * `previousViewportTop` field it does have is a private diffing internal, not
231
+ * something a Component can use). What `TUI` actually does is redraw the last
232
+ * `terminalRows` lines of *whatever this function returns this frame* — so
233
+ * "scrolling" only has one degree of freedom available: change how many lines
234
+ * this function returns, in front of a renderer that always anchors to the
235
+ * bottom of that array.
236
+ *
237
+ * The bug this used to have: the earlier version dropped lines straight off
238
+ * the end of `stream` as `scrollBack` grew, which shrinks the *whole returned
239
+ * array* by that much every keypress — since the status block sits at the very
240
+ * end, its position relative to the top of the (now shorter) array moves every
241
+ * time, and because the renderer bottom-anchors, that reads as the status
242
+ * block itself sliding up the screen the moment you press Up, before there's
243
+ * any real "scrolling" sensation at all. A user reported exactly this: "方向键
244
+ * 上去看的时候,不是滚动看,而是下面分割线的布局先往上跑."
245
+ *
246
+ * The fix: keep the returned array's total length **invariant to
247
+ * `scrollBack`** by left-padding with blank lines instead of shrinking the
248
+ * array. The status block then always sits at the same offset from the end of
249
+ * the document no matter how far back you've scrolled, so the bottom-anchored
250
+ * renderer keeps it visually still — only the *content* revealed above it
251
+ * changes as you page up, which is what "scrolling" is supposed to look like
252
+ * within a renderer that has no actual scroll primitive.
253
+ */
254
+ export function renderScreen(m, width, scrollBack = 0, now = Date.now()) {
255
+ const stream = renderStream(m, width);
256
+ const status = renderStatus(m, width, now);
257
+ const visibleCount = Math.max(0, stream.length - scrollBack);
258
+ const revealed = stream.slice(0, visibleCount);
259
+ const padding = new Array(stream.length - revealed.length).fill("");
260
+ return [...padding, ...revealed, "", ...status];
261
+ }
262
+ function truncate(s, width) {
263
+ const clean = s.replace(/\s+/g, " ").trim();
264
+ return visibleWidth(clean) <= width ? clean : truncateToWidth(clean, Math.max(1, width));
265
+ }
266
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/tui/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,UAAU,EAAE,SAAS,GAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/E,iFAAiF;AAEjF,MAAM,GAAG,GAAG,OAAO,CAAC;AACpB,MAAM,KAAK,GAAG;IACZ,KAAK,EAAE,GAAG,GAAG,IAAI;IACjB,GAAG,EAAE,GAAG,GAAG,IAAI;IACf,IAAI,EAAE,GAAG,GAAG,IAAI;IAChB,GAAG,EAAE,GAAG,GAAG,KAAK;IAChB,KAAK,EAAE,GAAG,GAAG,KAAK;IAClB,MAAM,EAAE,GAAG,GAAG,KAAK;IACnB,IAAI,EAAE,GAAG,GAAG,KAAK;IACjB,OAAO,EAAE,GAAG,GAAG,KAAK;IACpB,IAAI,EAAE,GAAG,GAAG,KAAK;IACjB,IAAI,EAAE,GAAG,GAAG,KAAK;CAClB,CAAC;AACF,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,CAAS,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;AACnE,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAClD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACtD,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAClD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACxD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,KAAa;IAC9C,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;YAClC,KAAK,MAAM,CAAC,IAAI,OAAO;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AAEjF,MAAM,SAAS,GAA+B;IAC5C,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC;IAClB,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC;IACnB,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;CACjB,CAAC;AAEF,SAAS,QAAQ,CAAC,MAAkB;IAClC,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,CAAW,EAAE,KAAa,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IAClF,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtH,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC/B,IAAI,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAClE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,CAAW,EAAE,IAAc,EAAE,GAAW;IAC3D,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxG,MAAM,OAAO,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1G,OAAO,GAAG,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;AACxF,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,EAAU;IAC/B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,OAAO,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC;AACzF,CAAC;AAED,iFAAiF;AAEjF,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,CAAc,EAAE,KAAa;IACvD,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,OAAO;YACV,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QACnK,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;YACzC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;QACpC,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YACvC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChC,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC/E,KAAK,MAAM,EAAE,IAAI,WAAW;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3D,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,IAAI,CAAC,CAAC,KAAK;gBAAE,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACrD,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACxD,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7B,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,CAAyC;IACzD,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/F,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC;AACnC,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,KAAa;IACrD,MAAM,MAAM,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AAEjF,MAAM,WAAW,GAA8B;IAC7C,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC;IAC1B,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;CACvB,CAAC;AAEF,kFAAkF;AAClF,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,KAAa,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IAC/E,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjC,+BAA+B;IAC/B,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjI,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAChC,uEAAuE;IACvE,4EAA4E;IAC5E,gDAAgD;IAChD,GAAG,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5C,0EAA0E;IAC1E,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAChD,yCAAyC;IACzC,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,aAAa;IACb,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAC3C,OAAO,GAAG,CAAC;AACb,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,KAAa;IACrD,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO;YACL,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC;YAC/C,GAAG,IAAI,CAAC,iBAAiB,IAAI,CAAC,qBAAqB,CAAC,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC;SAClG,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACpD,OAAO;YACL,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC;YAC7C,GAAG,IAAI,CAAC,oBAAoB,IAAI,CAAC,qBAAqB,CAAC,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC;SACrG,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,QAAQ,CAAC,CAAW;IAC3B,IAAI,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC3C,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO;QAAE,OAAO,gCAAgC,CAAC;IACvE,IAAI,SAAS,CAAC,CAAC,CAAC;QAAE,OAAO,wBAAwB,CAAC;IAClD,OAAO,wCAAwC,CAAC;AAClD,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,KAAa,EAAE,UAAU,GAAG,CAAC,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IAC/F,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,KAAa;IACxC,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,OAAO,YAAY,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The dedicated run view: renders one workflow instance running, live.
3
+ *
4
+ * `runInstanceInTui` is the reusable core — given an existing engine/runner
5
+ * and a TUI to render into, it drives one instance to completion/cancellation
6
+ * or until the user detaches, translating keystrokes into engine actions
7
+ * (approve / revise / resume / cancel / steer). The runner drives the
8
+ * workflow autonomously; this file just shows it and steers it. No LLM sits
9
+ * in the loop.
10
+ *
11
+ * Two callers:
12
+ * - `runApp(cwd)`: the standalone CLI path (own process, own TUI, own
13
+ * launcher to pick a workflow). Kept for direct invocation; no longer the
14
+ * default `ralphflow` entry point (see cli.ts) — that's now the chat.
15
+ * - `attachRunView` (tui/embed.ts): the chat session hands this its OWN
16
+ * already-running TUI/engine/runner via `ctx.ui.custom()`, so starting a
17
+ * workflow from chat takes over the SAME terminal instead of a second one.
18
+ */
19
+ import type { Engine } from "../engine/core.js";
20
+ import { type Runner } from "../engine/runner.js";
21
+ import { TUI } from "../pi/tui.js";
22
+ export declare function runApp(cwd: string): Promise<void>;
23
+ export interface RunInstanceOutcome {
24
+ /**
25
+ * "completed"/"cancelled" mirror the instance's terminal fate. "detached"
26
+ * means the user left the run view (Esc on an empty input) while the
27
+ * workflow was still gate/paused/stalled/running — the runner keeps
28
+ * driving it untouched; nothing here pauses or aborts anything on detach.
29
+ */
30
+ outcome: "completed" | "cancelled" | "detached";
31
+ reportPath?: string;
32
+ }
33
+ /**
34
+ * Drive one instance to its end (or the user detaching), rendering it live
35
+ * into `tui`. Does not own `engine`/`runner`/`sessionId`/`tui` — the caller
36
+ * decides their lifetime, which is what lets this run against either a
37
+ * process's own dedicated TUI (`runApp`) or a chat session's TUI borrowed via
38
+ * `ctx.ui.custom()` (`attachRunView`).
39
+ */
40
+ export declare function runInstanceInTui(opts: {
41
+ engine: Engine;
42
+ tui: TUI;
43
+ sessionId: string;
44
+ instId: string;
45
+ runner: Runner;
46
+ }): Promise<RunInstanceOutcome>;
47
+ /** Fold an attachNote'd note into whatever failure reason a resume already carries. */
48
+ export declare function combineReason(prevReason: string | undefined, note: string | undefined): string | undefined;
49
+ //# sourceMappingURL=run-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-app.d.ts","sourceRoot":"","sources":["../../src/tui/run-app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,EAAgB,KAAK,MAAM,EAAqB,MAAM,qBAAqB,CAAC;AAUnF,OAAO,EAAE,GAAG,EAA8B,MAAM,cAAc,CAAC;AAsB/D,wBAAsB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCvD;AAqCD,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,GAAG,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqF9B;AAqBD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG1G"}
@@ -0,0 +1,317 @@
1
+ /**
2
+ * The dedicated run view: renders one workflow instance running, live.
3
+ *
4
+ * `runInstanceInTui` is the reusable core — given an existing engine/runner
5
+ * and a TUI to render into, it drives one instance to completion/cancellation
6
+ * or until the user detaches, translating keystrokes into engine actions
7
+ * (approve / revise / resume / cancel / steer). The runner drives the
8
+ * workflow autonomously; this file just shows it and steers it. No LLM sits
9
+ * in the loop.
10
+ *
11
+ * Two callers:
12
+ * - `runApp(cwd)`: the standalone CLI path (own process, own TUI, own
13
+ * launcher to pick a workflow). Kept for direct invocation; no longer the
14
+ * default `ralphflow` entry point (see cli.ts) — that's now the chat.
15
+ * - `attachRunView` (tui/embed.ts): the chat session hands this its OWN
16
+ * already-running TUI/engine/runner via `ctx.ui.custom()`, so starting a
17
+ * workflow from chat takes over the SAME terminal instead of a second one.
18
+ */
19
+ import fs from "fs";
20
+ import { createEngine } from "../engine/core.js";
21
+ import { abortActiveCheck } from "../engine/check.js";
22
+ import { createRunner } from "../engine/runner.js";
23
+ import { withInstanceLock, isInstanceGone } from "../engine/lock.js";
24
+ import { loadSkillIndex } from "../engine/skills.js";
25
+ import { isSubWorkflowStep } from "../engine/types.js";
26
+ import { applyCancelled, applyCompleted, applyGate, applyPaused, applyStalled, applyStepEvent, applyStepStart, applyUserMessage, applyVerdict, initRunModel, primeForAttach, } from "./run-model.js";
27
+ import { runLauncher } from "./launcher.js";
28
+ import { createRunView } from "./run-view.js";
29
+ import { TUI, ProcessTerminal, initTheme } from "../pi/tui.js";
30
+ /** A stable owner token for this process's run. */
31
+ function makeSessionId() {
32
+ return `ralphflow-run-${process.pid}-${Date.now().toString(36)}`;
33
+ }
34
+ /**
35
+ * Terminal bell (BEL, \x07) on every state a human might need to come back
36
+ * for: gate, paused, stalled, completed. Cheapest possible "come look" signal
37
+ * that works identically whether the run view is the whole process (runApp)
38
+ * or a chat session's borrowed terminal (attachRunView) — most terminals/
39
+ * terminal multiplexers turn this into a visible tab/window flash or an
40
+ * audible ding even when the pane isn't focused, which a rendered line never
41
+ * achieves on its own. No config flag: this fires at most once per state
42
+ * transition (called from the same event handler that already renders once),
43
+ * never in a loop, so there is no plausible "too noisy" failure mode to gate.
44
+ */
45
+ function bell() {
46
+ try {
47
+ process.stdout.write("\x07");
48
+ }
49
+ catch { }
50
+ }
51
+ export async function runApp(cwd) {
52
+ initTheme();
53
+ const engine = createEngine(cwd, { abortActiveCheck });
54
+ engine.ensureProjectWorkflows();
55
+ const sessionId = makeSessionId();
56
+ const tui = new TUI(new ProcessTerminal());
57
+ tui.start();
58
+ const runner = createRunner(engine, {}, {
59
+ skillPaths: () => loadSkillIndex(engine.getRalphFlowDir(), engine.getGlobalConfigHome()).paths,
60
+ });
61
+ engine.setAbortActiveStep?.(runner.abortActiveStep);
62
+ // Park interrupted instances on the way out so a restart resumes cleanly.
63
+ let shuttingDown = false;
64
+ const shutdown = () => {
65
+ if (shuttingDown)
66
+ return;
67
+ shuttingDown = true;
68
+ try {
69
+ runner.pauseAllForShutdown();
70
+ }
71
+ catch { }
72
+ try {
73
+ tui.stop();
74
+ }
75
+ catch { }
76
+ };
77
+ process.on("SIGINT", () => { shutdown(); process.exit(0); });
78
+ process.on("SIGTERM", () => { shutdown(); process.exit(0); });
79
+ try {
80
+ const choice = await runLauncher(tui, engine);
81
+ if (choice.kind === "quit")
82
+ return;
83
+ const instId = choice.kind === "resume"
84
+ ? choice.instanceId
85
+ : createInstance(engine, choice.workflow, choice.task, sessionId);
86
+ if (!instId)
87
+ return; // creation failed (surfaced by createInstance)
88
+ await runInstanceInTui({ engine, tui, sessionId, instId, runner });
89
+ }
90
+ finally {
91
+ shutdown();
92
+ }
93
+ }
94
+ /**
95
+ * Create a fresh instance owned by this session and cache its first DO prompt.
96
+ * Mirrors ralphflow_start's creation path (normal or sub-workflow first step),
97
+ * minus the chat-surface text and auto-run — the run app drives it itself.
98
+ */
99
+ function createInstance(engine, workflow, task, sessionId) {
100
+ const wf = engine.loadWorkflow(workflow);
101
+ if (!wf || wf.steps.length === 0)
102
+ return null;
103
+ const firstStep = wf.steps[0];
104
+ const instId = engine.generateInstanceId(workflow);
105
+ fs.mkdirSync(engine.getInstanceDir(instId), { recursive: true });
106
+ engine.writeArtifactsDirName(instId, task);
107
+ const baseState = {
108
+ active: true, workflow_name: workflow, current_step: firstStep.id, current_phase: "do",
109
+ fail_count: 0, user_task: task, paused: false, session_id: sessionId,
110
+ };
111
+ engine.writeState(baseState, instId);
112
+ engine.recordStepStart(instId, firstStep.id, "do");
113
+ engine.logEvent(instId, "info", "workflow_start", { workflow, instance: instId });
114
+ if (isSubWorkflowStep(firstStep)) {
115
+ engine.pushState(baseState, instId);
116
+ const sub = engine.resolveSubWorkflowEntry(instId, firstStep.workflow, task, firstStep);
117
+ if (sub.error) {
118
+ try {
119
+ fs.rmSync(engine.getInstanceDir(instId), { recursive: true, force: true });
120
+ }
121
+ catch { }
122
+ return null;
123
+ }
124
+ }
125
+ else {
126
+ if (wf.manual_step.includes(firstStep.id))
127
+ engine.writeManualStepMarker(instId);
128
+ engine.buildDoPrompt(instId, firstStep, task);
129
+ }
130
+ return instId;
131
+ }
132
+ /**
133
+ * Drive one instance to its end (or the user detaching), rendering it live
134
+ * into `tui`. Does not own `engine`/`runner`/`sessionId`/`tui` — the caller
135
+ * decides their lifetime, which is what lets this run against either a
136
+ * process's own dedicated TUI (`runApp`) or a chat session's TUI borrowed via
137
+ * `ctx.ui.custom()` (`attachRunView`).
138
+ */
139
+ export function runInstanceInTui(opts) {
140
+ const { engine, tui, sessionId, instId, runner } = opts;
141
+ return new Promise((resolve) => {
142
+ const workflow = engine.loadWorkflow(engine.readState(instId).workflow_name);
143
+ const model = buildInitialModel(engine, instId, workflow);
144
+ // Reassigned to view.requestRender once the view exists (it also syncs the
145
+ // persistent input's mount/focus state — see run-view.ts).
146
+ let render = () => tui.requestRender();
147
+ /** Text left at a paused/stalled state via attachNote, consumed on resume. */
148
+ const pendingResumeNotes = new Map();
149
+ let removeInputListener = null;
150
+ let removeRunnerListener = null;
151
+ // Everything else re-renders reactively off runner events, but elapsed
152
+ // time needs a clock tick even during a long silent stretch (the model
153
+ // reasoning with no tool calls at all) — otherwise the timer freezes and
154
+ // looks exactly like the stuck-vs-thinking ambiguity it exists to solve.
155
+ // Cheap to over-fire: renderStepStrip only shows elapsed while a phase is
156
+ // active, and pi-tui's differential renderer no-ops an unchanged frame.
157
+ const tickTimer = setInterval(() => { if (model.activePhase)
158
+ render(); }, 1000);
159
+ let done = false;
160
+ const finish = () => {
161
+ if (done)
162
+ return;
163
+ done = true;
164
+ clearInterval(tickTimer);
165
+ removeInputListener?.();
166
+ removeRunnerListener?.();
167
+ resolve({
168
+ outcome: model.status === "completed" ? "completed" : model.status === "cancelled" ? "cancelled" : "detached",
169
+ reportPath: model.reportPath,
170
+ });
171
+ };
172
+ // Structured events → the pure model → a render request. No parsing, no LLM.
173
+ const events = {
174
+ onStepStart: (_i, sid, phase, attempt) => { applyStepStart(model, sid, phase, attempt); render(); },
175
+ onStepEvent: (_i, sid, phase, e) => { applyStepEvent(model, sid, phase, e); render(); },
176
+ onVerdict: (_i, sid, r) => { applyVerdict(model, sid, r); render(); },
177
+ onGate: (_i, sid) => { applyGate(model, sid); render(); bell(); },
178
+ onPaused: (_i, st) => { applyPaused(model, st); render(); bell(); },
179
+ onStalled: (_i, sid, a) => { applyStalled(model, sid, a); render(); bell(); },
180
+ onCompleted: (_i, rp) => { applyCompleted(model, rp); render(); bell(); },
181
+ };
182
+ removeRunnerListener = runner.addEventListener(events);
183
+ const view = createRunView({
184
+ tui,
185
+ getModel: () => model,
186
+ onQuit: finish,
187
+ actions: {
188
+ approveGate: () => { void continueInstance(engine, runner, sessionId, instId); },
189
+ reviseGate: (instruction) => runner.reviseGate(instId, instruction),
190
+ resume: () => {
191
+ const note = pendingResumeNotes.get(instId);
192
+ pendingResumeNotes.delete(instId);
193
+ void continueInstance(engine, runner, sessionId, instId, note);
194
+ },
195
+ cancel: () => { void cancelInstance(engine, runner, instId).then(() => { applyCancelled(model); render(); finish(); }); },
196
+ sendMessage: (text) => {
197
+ // Optimistic local echo — same style as the other actions here, no
198
+ // need to round-trip through a RunnerEvents callback for this.
199
+ const sid = model.activeStepId;
200
+ if (sid && model.activePhase === "do") {
201
+ applyUserMessage(model, sid, text);
202
+ render();
203
+ }
204
+ runner.sendUserMessage(instId, text);
205
+ },
206
+ attachNote: (text) => {
207
+ const note = text.trim();
208
+ if (!note)
209
+ return;
210
+ pendingResumeNotes.set(instId, note);
211
+ const sid = model.activeStepId;
212
+ if (sid) {
213
+ applyUserMessage(model, sid, note);
214
+ render();
215
+ }
216
+ },
217
+ },
218
+ });
219
+ render = view.requestRender;
220
+ // Put the view on screen and route keys to it.
221
+ tui.clear();
222
+ tui.addChild(view.screen);
223
+ render();
224
+ removeInputListener = tui.addInputListener((data) => (view.handleInput(data) ? { consume: true } : undefined));
225
+ // Kick off the run.
226
+ runner.ensureRunning(instId);
227
+ });
228
+ }
229
+ /** Build the initial model, reflecting a resumed instance's current position. */
230
+ function buildInitialModel(engine, instId, workflow) {
231
+ const state = engine.readState(instId);
232
+ const model = initRunModel(instId, workflow, state.user_task);
233
+ // Reflect where the instance actually is — this view may be attaching to
234
+ // one that's already running (ralphflow_watch, or ralphflow_continue
235
+ // landing back in the view), not one it just launched itself. See
236
+ // primeForAttach's own doc comment for why this is safe to call
237
+ // unconditionally, including for a genuinely fresh start.
238
+ const passedStepIds = [...new Set(engine.loadStepRecords(instId).filter((r) => r.phase === "check" && r.status === "passed").map((r) => r.stepId))];
239
+ primeForAttach(model, state, passedStepIds);
240
+ if (state.paused) {
241
+ applyPaused(model, state); // overrides activePhase back to null; keeps activeStepId/done-steps from above
242
+ }
243
+ return model;
244
+ }
245
+ /** Fold an attachNote'd note into whatever failure reason a resume already carries. */
246
+ export function combineReason(prevReason, note) {
247
+ if (!note)
248
+ return prevReason;
249
+ return prevReason ? `${prevReason}\n\n用户补充说明:\n${note}` : `用户补充说明:\n${note}`;
250
+ }
251
+ /** Approve a gate / resume a pause or stall — the ralphflow_continue branches, run-view side. */
252
+ async function continueInstance(engine, runner, sessionId, instId, note) {
253
+ try {
254
+ await withInstanceLock(engine.getInstanceDir(instId), instId, () => {
255
+ const state = engine.readState(instId);
256
+ if (!state || !state.active)
257
+ return;
258
+ engine.bindInstance(instId, sessionId);
259
+ // check_error pause → reset to check, re-verify.
260
+ if (state.paused && state.pause_reason === "check_error" && state.current_phase === "check") {
261
+ engine.writeState({ ...state, paused: false, pause_reason: undefined }, instId);
262
+ return;
263
+ }
264
+ // manual gate → clearing it IS the approval.
265
+ if (engine.markerExists(".manual-gate", instId)) {
266
+ engine.clearManualStepMarker(instId);
267
+ engine.clearManualGate(instId);
268
+ return;
269
+ }
270
+ // other pause → reset fail count, re-issue the DO prompt for a fresh attempt.
271
+ if (state.paused) {
272
+ const prevReason = combineReason(state.last_failure_reason, note);
273
+ const prevFail = state.fail_count;
274
+ engine.clearReinjectCounter(instId);
275
+ engine.clearDoneReported(instId);
276
+ engine.writeState({ ...state, current_phase: "do", paused: false, pause_reason: undefined, fail_count: 0 }, instId);
277
+ const step = engine.getStep(engine.loadWorkflow(state.workflow_name), state.current_step);
278
+ if (step && !isSubWorkflowStep(step))
279
+ engine.buildDoPrompt(instId, step, state.user_task, prevReason, prevFail);
280
+ return;
281
+ }
282
+ // stalled → the keep-alive budget ran out and the loop exited on its own
283
+ // WITHOUT pausing the state (runner.ts: "the workflow is fine, the model
284
+ // is just stuck"), so none of the branches above fire for it. Rearm the
285
+ // budget explicitly, and fold in a note the same way a real retry would.
286
+ if (state.current_phase === "do" && !runner.isRunning(instId)) {
287
+ engine.clearReinjectCounter(instId);
288
+ if (note) {
289
+ const step = engine.getStep(engine.loadWorkflow(state.workflow_name), state.current_step);
290
+ if (step && !isSubWorkflowStep(step)) {
291
+ engine.clearDoPromptCache(instId);
292
+ engine.buildDoPrompt(instId, step, state.user_task, note, state.fail_count || undefined);
293
+ }
294
+ }
295
+ }
296
+ });
297
+ }
298
+ catch (e) {
299
+ if (!isInstanceGone(e))
300
+ throw e;
301
+ }
302
+ if (engine.instanceExists(instId))
303
+ runner.ensureRunning(instId);
304
+ }
305
+ async function cancelInstance(engine, runner, instId) {
306
+ try {
307
+ await withInstanceLock(engine.getInstanceDir(instId), instId, () => {
308
+ runner.abortActiveStep(instId);
309
+ engine.destroyInstance(instId, "cancelled");
310
+ });
311
+ }
312
+ catch (e) {
313
+ if (!isInstanceGone(e))
314
+ throw e;
315
+ }
316
+ }
317
+ //# sourceMappingURL=run-app.js.map