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,114 @@
1
+ /**
2
+ * The ralph-flow extension for the main chat session.
3
+ *
4
+ * This is the whole TUI. The plan budgeted ~870 lines for a hand-built one
5
+ * (transcript pane, streaming renderer, slash dispatcher, status bar) — none of
6
+ * that got written, because pi's InteractiveMode already IS a Claude-Code-style
7
+ * TUI and its extension API exposes exactly the three seams we need:
8
+ *
9
+ * registerTool → the six ralphflow_* tools, so the model can drive them
10
+ * from plain language ("start the spec workflow on X")
11
+ * registerCommand → the eight /ralphflow-* slash commands, verbatim
12
+ * sendMessage → the runner's output, rendered into the same transcript
13
+ *
14
+ * The important thing this does NOT do: hand pi the workflow. Step sessions are
15
+ * still created and driven by our runner, one fresh context per step. The main
16
+ * chat session is a control surface and a viewport — if it were the DO session
17
+ * we would have rebuilt the opencode plugin's central flaw on a new stack.
18
+ */
19
+ import { createRunner } from "../engine/runner.js";
20
+ import { createTools } from "../commands/tools.js";
21
+ import { COMMAND_PROMPTS } from "../commands/prompts.js";
22
+ import { loadSkillIndex } from "../engine/skills.js";
23
+ import { historyEditorFactory } from "./history-editor.js";
24
+ import { createWelcomeHeaderFactory } from "./welcome-header.js";
25
+ /**
26
+ * Build the inline extension. `getSessionId` must return the main session's id
27
+ * — it is the ownership token written into every instance's state.json.
28
+ */
29
+ export function createRalphExtension(engine, getSessionId) {
30
+ let runner;
31
+ let api;
32
+ /** Post engine-authored text into the transcript without provoking a turn. */
33
+ const post = (text) => {
34
+ api.sendMessage({ customType: "ralph-flow", content: text, display: text }, { triggerTurn: false });
35
+ };
36
+ // Pi's InlineExtension shape: { name, factory } — the factory IS the activate
37
+ // hook, called with the extension API.
38
+ const extension = {
39
+ name: "ralph-flow",
40
+ factory(a) {
41
+ api = a;
42
+ // `ui` doesn't exist on `factory`'s own `api` (see the interface
43
+ // comment above) — "session_start" is the first point an
44
+ // ExtensionContext, and therefore `ctx.ui`, is available. `mode ===
45
+ // "tui"` guards it per ExtensionContext.mode's own doc comment ("Use
46
+ // 'tui' to guard terminal-only UI such as custom components") — rpc/
47
+ // print modes have no editor to replace.
48
+ api.on("session_start", (_event, ctx) => {
49
+ if (ctx.mode !== "tui")
50
+ return;
51
+ ctx.ui.setEditorComponent(historyEditorFactory);
52
+ ctx.ui.setHeader(createWelcomeHeaderFactory(() => engine.listWorkflows()));
53
+ });
54
+ // Deliberately NO onStepEvent here: run-app.ts's own temporary listener
55
+ // (added only while a run view is actually attached — see embed.ts)
56
+ // already shows every DO/CHECK tool call live for anyone watching. A
57
+ // permanent listener that also streamed those one-liners into chat used
58
+ // to sit here — real-terminal testing found it was *why* the model kept
59
+ // reaching for ralphflow_watch on totally unrelated turns: those lines
60
+ // ("▸ [create] bash mkdir -p ...", "▸ [create] report_done") accumulate
61
+ // in the transcript the model re-reads on every future invocation
62
+ // (`triggerTurn: false` only skips waking it up immediately — the text
63
+ // is still there next time it IS woken up, for any reason), and a model
64
+ // that notices background activity in its own context tends to comment
65
+ // on or act on it, unprompted. This directly violated the product's own
66
+ // stated promise ("不需要你现在做任何事,也不需要主动去查看或汇报进度")
67
+ // — DO/CHECK tool calls were never a "needs you" moment. Removing this
68
+ // loses nothing for someone actually watching; it only stops narrating
69
+ // to someone who isn't.
70
+ runner = createRunner(engine, {
71
+ onMessage: (_instId, text) => post(text),
72
+ onGate: () => { }, // the gate's own message comes through onMessage
73
+ onPaused: () => { }, // ditto
74
+ onStalled: () => { }, // ditto
75
+ onCompleted: () => { }, // ditto
76
+ }, {
77
+ skillPaths: () => loadSkillIndex(engine.getRalphFlowDir(), engine.getGlobalConfigHome()).paths,
78
+ });
79
+ // The engine needs to reach into this process to abort a DO session when
80
+ // an instance is destroyed. (abortActiveCheck is wired at engine creation.)
81
+ engine.setAbortActiveStep?.(runner.abortActiveStep);
82
+ for (const tool of createTools({ engine, runner, getSessionId })) {
83
+ api.registerTool(tool);
84
+ }
85
+ // Slash commands are thin: each hands its prompt template to the model,
86
+ // which then calls the matching tool. Keeping the model in the loop is
87
+ // what makes "/ralphflow-start" with no arguments ask what you want, and
88
+ // what makes /ralphflow-create an actual conversation.
89
+ for (const [name, template] of Object.entries(COMMAND_PROMPTS)) {
90
+ api.registerCommand(name, {
91
+ description: template.description,
92
+ handler: async (args) => {
93
+ api.sendUserMessage(template.render(args));
94
+ },
95
+ });
96
+ }
97
+ // Adopt whatever was left running: a crashed process leaves live instance
98
+ // dirs behind, and the user should not have to know to type continue.
99
+ for (const info of engine.listInstances()) {
100
+ if (info.state.paused)
101
+ continue;
102
+ if (engine.foreignRunnerPid(info.id) !== null)
103
+ continue; // someone else has it
104
+ runner.ensureRunning(info.id);
105
+ }
106
+ },
107
+ };
108
+ return {
109
+ extension,
110
+ get runner() { return runner; },
111
+ shutdown: () => runner?.pauseAllForShutdown(),
112
+ };
113
+ }
114
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../../src/tui/extension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Completes a feature pi's own chat editor ships half-wired: `Editor` (the
3
+ * base class `CustomEditor` extends, in pi-tui) already implements Up/Down
4
+ * arrow history navigation in full — `history`/`historyIndex`/`navigateHistory`
5
+ * and the key-handling branches that call it are all there, and `addToHistory`
6
+ * is documented "Called after successful submission." Nothing in
7
+ * pi-coding-agent's interactive mode actually calls it, though (verified by
8
+ * grepping the compiled output of all four @earendil-works packages for
9
+ * `addToHistory` — the only hit is the definition itself). So the editor never
10
+ * has anything to navigate to, even though the navigation logic works.
11
+ *
12
+ * The obvious hook points don't work: `submitValue()` (where the base class's
13
+ * own doc comment says addToHistory should be called) is `private` in the
14
+ * .d.ts, so a subclass can't override or call it. `onSubmit` looks like a
15
+ * public callback property to wrap, but the base class declares it as a bare
16
+ * class field (`onSubmit;`, no initializer) — under ES2022 class-field
17
+ * semantics that materializes as an own `[[DefineOwnProperty]]` on every
18
+ * instance during `super()`, which would silently SHADOW a get/set accessor
19
+ * defined in this subclass rather than route through it.
20
+ *
21
+ * What's actually safe: override the public `handleInput(data)` (already
22
+ * overridden once by `CustomEditor` itself, so this is a well-trodden
23
+ * extension point) and replicate the base class's own submit-detection using
24
+ * only its public surface (`getLines`/`getCursor`/`disableSubmit`/
25
+ * `getExpandedText`/`addToHistory` — none of them private). The backslash-
26
+ * before-cursor check mirrors submitValue's own "Shift+Enter workaround"
27
+ * branch exactly, so a `\` + Enter that inserts a newline instead of
28
+ * submitting doesn't pollute history with a half-typed draft.
29
+ */
30
+ import { CustomEditor, type EditorTheme, type KeybindingsManager, type TUI } from "../pi/tui.js";
31
+ export declare class HistoryEditor extends CustomEditor {
32
+ private kb;
33
+ constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager);
34
+ handleInput(data: string): void;
35
+ }
36
+ /** Matches Pi's `EditorFactory` shape for `ctx.ui.setEditorComponent(...)`. */
37
+ export declare function historyEditorFactory(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager): HistoryEditor;
38
+ //# sourceMappingURL=history-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-editor.d.ts","sourceRoot":"","sources":["../../src/tui/history-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,GAAG,EAAE,MAAM,cAAc,CAAC;AAEjG,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,EAAE,CAAqB;gBAEnB,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB;IAKhE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAYzC;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,GAAG,aAAa,CAEjH"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Completes a feature pi's own chat editor ships half-wired: `Editor` (the
3
+ * base class `CustomEditor` extends, in pi-tui) already implements Up/Down
4
+ * arrow history navigation in full — `history`/`historyIndex`/`navigateHistory`
5
+ * and the key-handling branches that call it are all there, and `addToHistory`
6
+ * is documented "Called after successful submission." Nothing in
7
+ * pi-coding-agent's interactive mode actually calls it, though (verified by
8
+ * grepping the compiled output of all four @earendil-works packages for
9
+ * `addToHistory` — the only hit is the definition itself). So the editor never
10
+ * has anything to navigate to, even though the navigation logic works.
11
+ *
12
+ * The obvious hook points don't work: `submitValue()` (where the base class's
13
+ * own doc comment says addToHistory should be called) is `private` in the
14
+ * .d.ts, so a subclass can't override or call it. `onSubmit` looks like a
15
+ * public callback property to wrap, but the base class declares it as a bare
16
+ * class field (`onSubmit;`, no initializer) — under ES2022 class-field
17
+ * semantics that materializes as an own `[[DefineOwnProperty]]` on every
18
+ * instance during `super()`, which would silently SHADOW a get/set accessor
19
+ * defined in this subclass rather than route through it.
20
+ *
21
+ * What's actually safe: override the public `handleInput(data)` (already
22
+ * overridden once by `CustomEditor` itself, so this is a well-trodden
23
+ * extension point) and replicate the base class's own submit-detection using
24
+ * only its public surface (`getLines`/`getCursor`/`disableSubmit`/
25
+ * `getExpandedText`/`addToHistory` — none of them private). The backslash-
26
+ * before-cursor check mirrors submitValue's own "Shift+Enter workaround"
27
+ * branch exactly, so a `\` + Enter that inserts a newline instead of
28
+ * submitting doesn't pollute history with a half-typed draft.
29
+ */
30
+ import { CustomEditor } from "../pi/tui.js";
31
+ export class HistoryEditor extends CustomEditor {
32
+ kb;
33
+ constructor(tui, theme, keybindings) {
34
+ super(tui, theme, keybindings);
35
+ this.kb = keybindings;
36
+ }
37
+ handleInput(data) {
38
+ if (!this.disableSubmit && this.kb.matches(data, "tui.input.submit")) {
39
+ const cursor = this.getCursor();
40
+ const currentLine = this.getLines()[cursor.line] || "";
41
+ const isBackslashContinuation = cursor.col > 0 && currentLine[cursor.col - 1] === "\\";
42
+ if (!isBackslashContinuation) {
43
+ const text = this.getExpandedText().trim();
44
+ if (text)
45
+ this.addToHistory(text);
46
+ }
47
+ }
48
+ super.handleInput(data);
49
+ }
50
+ }
51
+ /** Matches Pi's `EditorFactory` shape for `ctx.ui.setEditorComponent(...)`. */
52
+ export function historyEditorFactory(tui, theme, keybindings) {
53
+ return new HistoryEditor(tui, theme, keybindings);
54
+ }
55
+ //# sourceMappingURL=history-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-editor.js","sourceRoot":"","sources":["../../src/tui/history-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAuD,MAAM,cAAc,CAAC;AAEjG,MAAM,OAAO,aAAc,SAAQ,YAAY;IACrC,EAAE,CAAqB;IAE/B,YAAY,GAAQ,EAAE,KAAkB,EAAE,WAA+B;QACvE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,GAAG,WAAW,CAAC;IACxB,CAAC;IAEQ,WAAW,CAAC,IAAY;QAC/B,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,CAAC;YACrE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAChC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACvD,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;YACvF,IAAI,CAAC,uBAAuB,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC,IAAI,EAAE,CAAC;gBAC3C,IAAI,IAAI;oBAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,UAAU,oBAAoB,CAAC,GAAQ,EAAE,KAAkB,EAAE,WAA+B;IAChG,OAAO,IAAI,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The launch flow: pick a workflow, describe the task — a form, not a chat.
3
+ *
4
+ * A deterministic pipeline is launched, not conversed with, so the entry point
5
+ * is a picker + a text field, not an LLM turn. (Designing a NEW workflow IS a
6
+ * conversation — that lives in the separate `ralphflow create` mode.)
7
+ *
8
+ * These helpers drive the shared TUI: they swap in a picker / input as the root,
9
+ * resolve a promise on selection, and leave the TUI clean for the run view.
10
+ */
11
+ import type { Engine } from "../engine/core.js";
12
+ import { TUI } from "../pi/tui.js";
13
+ export interface LaunchChoice {
14
+ kind: "start" | "resume" | "quit";
15
+ workflow?: string;
16
+ task?: string;
17
+ instanceId?: string;
18
+ }
19
+ /**
20
+ * Full launch flow. Resolves with the user's choice. Resume takes priority: if
21
+ * live instances exist, offer to attach before starting something new.
22
+ */
23
+ export declare function runLauncher(tui: TUI, engine: Engine): Promise<LaunchChoice>;
24
+ //# sourceMappingURL=launcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../src/tui/launcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAiD,GAAG,EAAuC,MAAM,cAAc,CAAC;AAGvH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAaD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAejF"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The launch flow: pick a workflow, describe the task — a form, not a chat.
3
+ *
4
+ * A deterministic pipeline is launched, not conversed with, so the entry point
5
+ * is a picker + a text field, not an LLM turn. (Designing a NEW workflow IS a
6
+ * conversation — that lives in the separate `ralphflow create` mode.)
7
+ *
8
+ * These helpers drive the shared TUI: they swap in a picker / input as the root,
9
+ * resolve a promise on selection, and leave the TUI clean for the run view.
10
+ */
11
+ import { Input, SelectList, Text, getSelectListTheme } from "../pi/tui.js";
12
+ import { bold, dim } from "./render.js";
13
+ /** Swap the TUI root to a single component (clearing prior children). */
14
+ function setRoot(tui, ...components) {
15
+ tui.clear();
16
+ for (const c of components)
17
+ tui.addChild(c);
18
+ tui.requestRender();
19
+ }
20
+ function heading(text) {
21
+ return new Text(text, 1, 1);
22
+ }
23
+ /**
24
+ * Full launch flow. Resolves with the user's choice. Resume takes priority: if
25
+ * live instances exist, offer to attach before starting something new.
26
+ */
27
+ export async function runLauncher(tui, engine) {
28
+ const instances = engine.listInstances();
29
+ if (instances.length > 0) {
30
+ const resumeChoice = await pickResumeOrNew(tui, engine, instances);
31
+ if (resumeChoice.kind !== "start")
32
+ return resumeChoice; // resume or quit
33
+ // fall through to the new-workflow flow
34
+ }
35
+ const workflow = await pickWorkflow(tui, engine);
36
+ if (!workflow)
37
+ return { kind: "quit" };
38
+ const task = await enterTask(tui, workflow);
39
+ if (task === null)
40
+ return { kind: "quit" };
41
+ return { kind: "start", workflow, task };
42
+ }
43
+ /** When instances already exist: resume one, start new, or quit. */
44
+ function pickResumeOrNew(tui, engine, instances) {
45
+ return new Promise((resolve) => {
46
+ const items = [
47
+ ...instances.map((i) => ({
48
+ value: `resume:${i.id}`,
49
+ label: `恢复 ${i.state.workflow_name} · ${i.state.current_step}`,
50
+ description: (i.state.user_task || "").replace(/\s+/g, " ").slice(0, 60),
51
+ })),
52
+ { value: "new", label: "开始一个新工作流", description: "选择工作流并输入任务" },
53
+ ];
54
+ const list = new SelectList(items, 10, getSelectListTheme());
55
+ list.onSelect = (item) => {
56
+ if (item.value === "new")
57
+ resolve({ kind: "start" });
58
+ else
59
+ resolve({ kind: "resume", instanceId: item.value.slice("resume:".length) });
60
+ };
61
+ list.onCancel = () => resolve({ kind: "quit" });
62
+ setRoot(tui, heading(bold("有未完成的工作流") + dim(" ↑↓ 选择 · 回车确认 · Esc 退出")), list);
63
+ tui.setFocus(list);
64
+ });
65
+ }
66
+ /** Pick a workflow from the resolved list (project → global → built-in). */
67
+ function pickWorkflow(tui, engine) {
68
+ return new Promise((resolve) => {
69
+ const workflows = engine.listWorkflows().filter((w) => !w.invalid);
70
+ if (workflows.length === 0) {
71
+ resolve(null);
72
+ return;
73
+ }
74
+ const items = workflows.map((w) => ({ value: w.name, label: w.name, description: w.desc }));
75
+ const list = new SelectList(items, 12, getSelectListTheme());
76
+ list.onSelect = (item) => resolve(item.value);
77
+ list.onCancel = () => resolve(null);
78
+ setRoot(tui, heading(bold("选择工作流") + dim(" ↑↓ 选择 · 回车确认 · Esc 退出")), list);
79
+ tui.setFocus(list);
80
+ });
81
+ }
82
+ /** Capture the task description. Resolves null on Esc. */
83
+ function enterTask(tui, workflow) {
84
+ return new Promise((resolve) => {
85
+ const input = new Input();
86
+ input.placeholder = "描述这次要完成的任务,回车开始…";
87
+ input.onSubmit = (value) => {
88
+ const task = value.trim();
89
+ if (task)
90
+ resolve(task); // empty → keep waiting
91
+ };
92
+ input.onEscape = () => resolve(null);
93
+ setRoot(tui, heading(bold(`工作流:${workflow}`) + dim(" 回车开始 · Esc 退出")), new Text(dim("引擎会逐步执行,每步一个全新会话,独立验证。你可以随时看到进度。"), 1, 0), input);
94
+ tui.setFocus(input);
95
+ });
96
+ }
97
+ //# sourceMappingURL=launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.js","sourceRoot":"","sources":["../../src/tui/launcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
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 { type RunModel, type StreamBlock } from "./run-model.js";
16
+ export declare const dim: (s: string) => string;
17
+ export declare const bold: (s: string) => string;
18
+ export declare const green: (s: string) => string;
19
+ export declare const red: (s: string) => string;
20
+ export declare const yellow: (s: string) => string;
21
+ export declare const cyan: (s: string) => string;
22
+ export declare const gray: (s: string) => string;
23
+ export declare const magenta: (s: string) => string;
24
+ /**
25
+ * Display width, counting CJK/emoji as 2 columns (delegates to pi-tui so it
26
+ * agrees exactly with the width pi-tui's renderer enforces — a mismatch makes
27
+ * the TUI throw "line exceeds terminal width").
28
+ */
29
+ export declare function visibleLength(s: string): number;
30
+ /**
31
+ * Wrap a logical line to `width` display columns, ANSI- and wide-char-aware.
32
+ * Uses pi-tui's own wrapper, so every line we emit is guaranteed to fit.
33
+ */
34
+ export declare function wrap(text: string, width: number): string[];
35
+ /**
36
+ * Pipeline strip: an icon-only progress row, then a dedicated line naming
37
+ * the active step — number, description, DO/CHECK, elapsed. Two lines, not
38
+ * one, on purpose: cramming "步骤 3/7:技术方案设计 DO 45s" onto the same
39
+ * line as the icon row (the original design) made a user report they could
40
+ * only tell "some step is running" from a glance — the icons read fine as a
41
+ * progress bar, but the one piece of text answering "which step, doing
42
+ * what, DO or CHECK" was easy to miss appended after seven icons. Giving it
43
+ * its own line, led with the step's ordinal position, fixes that without
44
+ * adding new information — everything here was already computed, just
45
+ * under-emphasized. `now` defaults to the real clock but is an explicit
46
+ * param so tests stay deterministic — this is the only place render.ts
47
+ * touches wall-clock time at all.
48
+ */
49
+ export declare function renderStepStrip(m: RunModel, width: number, now?: number): string[];
50
+ /** Render one block. Reasoning is dimmed; tools show their key arg + a few result lines. */
51
+ export declare function renderBlock(b: StreamBlock, width: number): string[];
52
+ /** The active step's stream as lines. */
53
+ export declare function renderStream(m: RunModel, width: number): string[];
54
+ /** Header + step strip + action prompt + hints — always visible at the bottom. */
55
+ export declare function renderStatus(m: RunModel, width: number, now?: number): string[];
56
+ /** The actionable block for gate / pause / stall / completion. */
57
+ export declare function renderAction(m: RunModel, width: number): string[];
58
+ /**
59
+ * The complete document. pi-tui's `TUI` has no real scrollable viewport —
60
+ * there's no API for "pin this footer, scroll that region above it" (checked:
61
+ * `TUI extends Container`'s public surface has nothing scroll-related; the
62
+ * `previousViewportTop` field it does have is a private diffing internal, not
63
+ * something a Component can use). What `TUI` actually does is redraw the last
64
+ * `terminalRows` lines of *whatever this function returns this frame* — so
65
+ * "scrolling" only has one degree of freedom available: change how many lines
66
+ * this function returns, in front of a renderer that always anchors to the
67
+ * bottom of that array.
68
+ *
69
+ * The bug this used to have: the earlier version dropped lines straight off
70
+ * the end of `stream` as `scrollBack` grew, which shrinks the *whole returned
71
+ * array* by that much every keypress — since the status block sits at the very
72
+ * end, its position relative to the top of the (now shorter) array moves every
73
+ * time, and because the renderer bottom-anchors, that reads as the status
74
+ * block itself sliding up the screen the moment you press Up, before there's
75
+ * any real "scrolling" sensation at all. A user reported exactly this: "方向键
76
+ * 上去看的时候,不是滚动看,而是下面分割线的布局先往上跑."
77
+ *
78
+ * The fix: keep the returned array's total length **invariant to
79
+ * `scrollBack`** by left-padding with blank lines instead of shrinking the
80
+ * array. The status block then always sits at the same offset from the end of
81
+ * the document no matter how far back you've scrolled, so the bottom-anchored
82
+ * renderer keeps it visually still — only the *content* revealed above it
83
+ * changes as you page up, which is what "scrolling" is supposed to look like
84
+ * within a renderer that has no actual scroll primitive.
85
+ */
86
+ export declare function renderScreen(m: RunModel, width: number, scrollBack?: number, now?: number): string[];
87
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/tui/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAEL,KAAK,QAAQ,EAAkD,KAAK,WAAW,EAChF,MAAM,gBAAgB,CAAC;AAmBxB,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,WAAoB,CAAC;AAClD,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,WAAqB,CAAC;AACpD,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,WAAsB,CAAC;AACtD,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,WAAoB,CAAC;AAClD,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,WAAuB,CAAC;AACxD,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,WAAqB,CAAC;AACpD,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,WAAqB,CAAC;AACpD,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,WAAwB,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;GAGG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAS1D;AAkBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,EAAE,CAM9F;AAwBD,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA4BnE;AAQD,yCAAyC;AACzC,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAOjE;AAaD,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,EAAE,CAiB3F;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBjE;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,SAAI,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,EAAE,CAO3G"}