stageflow 0.8.0 → 0.12.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 (159) hide show
  1. package/README.md +24 -14
  2. package/dist/agent/activity.d.ts +9 -0
  3. package/dist/agent/agentBackend.d.ts +32 -0
  4. package/dist/agent/agentBackend.js +38 -0
  5. package/dist/agent/claudeActivity.d.ts +15 -0
  6. package/dist/agent/claudeActivity.js +58 -0
  7. package/dist/agent/claudeAdapter.d.ts +5 -0
  8. package/dist/agent/claudeAdapter.js +301 -0
  9. package/dist/agent/claudeSession.d.ts +18 -0
  10. package/dist/agent/claudeSession.js +54 -0
  11. package/dist/agent/claudeTools.d.ts +89 -0
  12. package/dist/agent/claudeTools.js +135 -0
  13. package/dist/agent/fakeAgent.js +24 -4
  14. package/dist/agent/piAdapter.d.ts +6 -3
  15. package/dist/agent/piAdapter.js +129 -21
  16. package/dist/agent/port.d.ts +50 -1
  17. package/dist/agent/resolveAgentPort.d.ts +26 -0
  18. package/dist/agent/resolveAgentPort.js +39 -0
  19. package/dist/cli/runCommand.js +2 -2
  20. package/dist/cli/runsCommand.d.ts +22 -0
  21. package/dist/cli/runsCommand.js +773 -0
  22. package/dist/cli/validateOutput.d.ts +2 -2
  23. package/dist/cli/validateOutput.js +2 -2
  24. package/dist/cli.d.ts +1 -1
  25. package/dist/cli.js +43 -5
  26. package/dist/config/browseCatalog.js +1 -1
  27. package/dist/config/createPipeline.d.ts +2 -1
  28. package/dist/config/createPipeline.js +34 -9
  29. package/dist/config/createStage.js +14 -9
  30. package/dist/config/loadPipeline.js +10 -1
  31. package/dist/config/loadStage.js +138 -2
  32. package/dist/config/loadStageflowManifest.js +14 -1
  33. package/dist/config/mergePipelineIncludes.d.ts +1 -0
  34. package/dist/config/mergePipelineIncludes.js +14 -1
  35. package/dist/config/normalizePipelineStageEntry.d.ts +7 -2
  36. package/dist/config/normalizePipelineStageEntry.js +49 -6
  37. package/dist/config/parseCompletionContract.d.ts +8 -0
  38. package/dist/config/parseCompletionContract.js +218 -0
  39. package/dist/config/parsePreEmitChecks.d.ts +3 -0
  40. package/dist/config/parsePreEmitChecks.js +65 -0
  41. package/dist/config/pipelineNeeds.d.ts +19 -0
  42. package/dist/config/pipelineNeeds.js +151 -0
  43. package/dist/config/pipelineStageKeys.js +16 -1
  44. package/dist/config/resolveForkEmitContext.d.ts +6 -2
  45. package/dist/config/resolveForkEmitContext.js +9 -2
  46. package/dist/config/resolvePipelineDag.d.ts +2 -1
  47. package/dist/config/resolvePipelineDag.js +182 -39
  48. package/dist/config/validateCatalog.d.ts +1 -1
  49. package/dist/config/validateCompletionContract.d.ts +4 -0
  50. package/dist/config/validateCompletionContract.js +52 -0
  51. package/dist/envelope/check.d.ts +1 -1
  52. package/dist/envelope/check.js +86 -13
  53. package/dist/envelope/cloneForks.d.ts +1 -1
  54. package/dist/envelope/cloneForks.js +24 -9
  55. package/dist/envelope/feedbackLoop.d.ts +4 -0
  56. package/dist/envelope/feedbackLoop.js +31 -0
  57. package/dist/envelope/payloadSchema.d.ts +7 -1
  58. package/dist/envelope/payloadSchema.js +170 -13
  59. package/dist/envelope/preEmitChecks.d.ts +29 -0
  60. package/dist/envelope/preEmitChecks.js +57 -0
  61. package/dist/hitl/qaTrail.d.ts +25 -0
  62. package/dist/hitl/qaTrail.js +51 -0
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.js +1 -1
  65. package/dist/mcp/catalogTools.js +4 -2
  66. package/dist/mcp/controlTools.js +72 -0
  67. package/dist/mcp/waitingGates.d.ts +2 -1
  68. package/dist/mcp/waitingGates.js +17 -1
  69. package/dist/package-meta.d.ts +1 -0
  70. package/dist/package-meta.js +1 -0
  71. package/dist/projection/projectRun.d.ts +4 -1
  72. package/dist/projection/projectRun.js +7 -0
  73. package/dist/prompt/feedbackLoopContext.d.ts +2 -0
  74. package/dist/prompt/feedbackLoopContext.js +17 -0
  75. package/dist/prompt/priorEnvelope.d.ts +2 -2
  76. package/dist/prompt/priorEnvelope.js +4 -1
  77. package/dist/runstore/pipelineDagSnapshot.d.ts +1 -0
  78. package/dist/runstore/pipelineDagSnapshot.js +51 -11
  79. package/dist/runstore/port.d.ts +185 -3
  80. package/dist/runstore/port.js +42 -3
  81. package/dist/runstore/runProjection.d.ts +7 -3
  82. package/dist/runstore/runProjection.js +30 -15
  83. package/dist/runstore/sqlite/SqliteRunStore.d.ts +25 -1
  84. package/dist/runstore/sqlite/SqliteRunStore.js +626 -8
  85. package/dist/runstore/sqlite/schema.d.ts +1 -1
  86. package/dist/runstore/sqlite/schema.js +99 -0
  87. package/dist/runstore/stageInstanceId.d.ts +1 -1
  88. package/dist/runstore/stageInstanceId.js +3 -2
  89. package/dist/runstore/trackProjection.js +59 -27
  90. package/dist/runstore/verificationHistory.d.ts +19 -0
  91. package/dist/runstore/verificationHistory.js +30 -0
  92. package/dist/runtime/cloneSchedule.d.ts +40 -4
  93. package/dist/runtime/cloneSchedule.js +212 -28
  94. package/dist/runtime/completionCheckRunner.d.ts +137 -0
  95. package/dist/runtime/completionCheckRunner.js +365 -0
  96. package/dist/runtime/completionCheckoutBaseline.d.ts +3 -0
  97. package/dist/runtime/completionCheckoutBaseline.js +44 -0
  98. package/dist/runtime/envelopeRouting.d.ts +10 -3
  99. package/dist/runtime/envelopeRouting.js +146 -5
  100. package/dist/runtime/feedbackLoopCoordinator.d.ts +74 -0
  101. package/dist/runtime/feedbackLoopCoordinator.js +446 -0
  102. package/dist/runtime/feedbackLoopDecision.d.ts +44 -0
  103. package/dist/runtime/feedbackLoopDecision.js +207 -0
  104. package/dist/runtime/feedbackLoopRoute.d.ts +5 -0
  105. package/dist/runtime/feedbackLoopRoute.js +13 -0
  106. package/dist/runtime/feedbackLoopSchedule.d.ts +38 -0
  107. package/dist/runtime/feedbackLoopSchedule.js +72 -0
  108. package/dist/runtime/forkGeneration.d.ts +72 -0
  109. package/dist/runtime/forkGeneration.js +295 -0
  110. package/dist/runtime/gitCheckoutCapability.d.ts +7 -0
  111. package/dist/runtime/gitCheckoutCapability.js +97 -0
  112. package/dist/runtime/manualRecoveryState.d.ts +40 -0
  113. package/dist/runtime/manualRecoveryState.js +73 -0
  114. package/dist/runtime/pipelineScheduler.d.ts +13 -1
  115. package/dist/runtime/pipelineScheduler.js +328 -45
  116. package/dist/runtime/replayLifecycle.d.ts +107 -0
  117. package/dist/runtime/replayLifecycle.js +168 -0
  118. package/dist/runtime/runManager.d.ts +61 -1
  119. package/dist/runtime/runManager.js +501 -7
  120. package/dist/runtime/runRetryCoordinator.d.ts +8 -1
  121. package/dist/runtime/runRetryCoordinator.js +47 -10
  122. package/dist/runtime/stageAttemptBootstrap.d.ts +3 -1
  123. package/dist/runtime/stageAttemptBootstrap.js +79 -1
  124. package/dist/runtime/stageProcessLauncher.d.ts +1 -1
  125. package/dist/runtime/stageRecovery.js +1 -1
  126. package/dist/runtime/stageRunner.d.ts +4 -1
  127. package/dist/runtime/stageRunner.js +29 -3
  128. package/dist/runtime/stageWorker.js +69 -2
  129. package/dist/runtime/stageWorkerProtocol.d.ts +1 -1
  130. package/dist/runtime/verifiedStageExecution.d.ts +29 -0
  131. package/dist/runtime/verifiedStageExecution.js +119 -0
  132. package/dist/server/bootstrap.js +1 -0
  133. package/dist/server/http.js +114 -0
  134. package/dist/server/operatorResults.js +2 -0
  135. package/dist/tools/askOperator.d.ts +161 -5
  136. package/dist/tools/askOperator.js +58 -25
  137. package/dist/tools/emitStageEnvelope.d.ts +12 -12
  138. package/dist/tools/emitStageEnvelope.js +112 -24
  139. package/dist/types/completion.d.ts +43 -0
  140. package/dist/types/completion.js +1 -0
  141. package/dist/types/envelope.d.ts +18 -0
  142. package/dist/types/forkChoice.d.ts +9 -4
  143. package/dist/types/forkChoice.js +1 -1
  144. package/dist/types/pipeline.d.ts +39 -2
  145. package/dist/types/preEmitCheck.d.ts +18 -0
  146. package/dist/types/preEmitCheck.js +1 -0
  147. package/dist/types/stage.d.ts +13 -0
  148. package/dist/types/stageflowManifest.d.ts +2 -0
  149. package/dist/ui/assets/index-CXGz4nL7.js +118 -0
  150. package/dist/ui/assets/{index-C3N7MXAC.css → index-uVHiae8s.css} +1 -1
  151. package/dist/ui/index.html +2 -2
  152. package/package.json +2 -1
  153. package/skills/stageflow/references/control-surface.md +4 -2
  154. package/skills/stageflow-author/references/catalog-mapping.md +26 -2
  155. package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
  156. package/skills/stageflow-run/SKILL.md +56 -25
  157. package/skills/stageflow-run/references/mcp-call.md +2 -2
  158. package/skills/stageflow-run/references/native-question-ui.md +78 -0
  159. package/dist/ui/assets/index-CSoGDA3A.js +0 -118
@@ -13,8 +13,8 @@
13
13
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&display=swap" />
14
14
  <link rel="icon" type="image/svg+xml" href="/stageflow-icon.svg" />
15
15
  <title>Stageflow</title>
16
- <script type="module" crossorigin src="/assets/index-CSoGDA3A.js"></script>
17
- <link rel="stylesheet" crossorigin href="/assets/index-C3N7MXAC.css">
16
+ <script type="module" crossorigin src="/assets/index-CXGz4nL7.js"></script>
17
+ <link rel="stylesheet" crossorigin href="/assets/index-uVHiae8s.css">
18
18
  </head>
19
19
  <body>
20
20
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stageflow",
3
- "version": "0.8.0",
3
+ "version": "0.12.0",
4
4
  "description": "Stageflow — CLI pipeline runtime for configurable stages on Pi",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,6 +60,7 @@
60
60
  "vitest": "^4.1.10"
61
61
  },
62
62
  "dependencies": {
63
+ "@anthropic-ai/claude-agent-sdk": "^0.3.263",
63
64
  "@earendil-works/pi-ai": "^0.84.1",
64
65
  "@earendil-works/pi-coding-agent": "^0.84.1",
65
66
  "@modelcontextprotocol/node": "^2.0.0",
@@ -11,7 +11,7 @@ node ../scripts/detect-host.mjs
11
11
  node ../scripts/detect-host.mjs --base-url http://127.0.0.1:3847
12
12
  ```
13
13
 
14
- The script `GET`s `{baseUrl}/api/health` (default `http://127.0.0.1:3847`, 1500 ms timeout). **up** means HTTP 200 and parseable JSON. Non-200, non-JSON, or timeout is **down**.
14
+ The script `GET`s `{baseUrl}/api/health` (default `http://127.0.0.1:3847`, 1500 ms timeout). **up** means HTTP 200 and parseable JSON. Non-200, non-JSON, or timeout is **down**. This probe is Stageflow host up/down only — it does not detect a coding-agent question UI. Gate presentation lives in [`../../stageflow-run/references/native-question-ui.md`](../../stageflow-run/references/native-question-ui.md).
15
15
 
16
16
  Stdout is one line: `up <baseUrl>` or `down <baseUrl>`. Exit `0` when up, `1` when down, `2` on usage error.
17
17
 
@@ -23,4 +23,6 @@ Use MCP tools over the Streamable HTTP endpoint at `{baseUrl}/mcp`. Tool names a
23
23
 
24
24
  ## When the host is down
25
25
 
26
- Use the `sf` CLI. Command names and flags live in [docs/cli-reference.md](../../../docs/cli-reference.md). Typical talking-job commands: `sf run`, `sf validate`, `sf envelope get`, `sf artifact read`, `sf providers`.
26
+ Use the `sf` CLI. Command names and flags live in [docs/cli-reference.md](../../../docs/cli-reference.md). Typical talking-job commands: `sf run`, `sf runs waiting`, `sf runs answer`, `sf runs wait`, `sf validate`, `sf envelope get`, `sf artifact read`, `sf providers`.
27
+
28
+ Probe before each mutating `sf runs` verb. If the probe is **up** or the command refuses because a host is up, continue that gate via MCP — do not start a second mutating writer, and do not start `sf mcp` as a disposable bridge.
@@ -16,18 +16,19 @@ Collision handling lives in [`catalog-write-conventions.md`](catalog-write-conve
16
16
 
17
17
  ## Sequencing
18
18
 
19
- `needs` is a single parent stage id. Fan-in (one stage needing two parents) is out of catalog shape.
19
+ `needs` is a single parent stage id, or an array of at least two parent ids when one step waits for several earlier steps. Strings default to `on: [succeeded]`. `{ id, on }` accepts a non-empty unique subset of `succeeded` | `failed` | `skipped`.
20
20
 
21
21
  | Human says | Pipeline shape |
22
22
  |---|---|
23
23
  | Steps in order | Each later stage `needs` the previous id. No `fork` field. |
24
24
  | Steps happen together | Sibling stages share one `needs` (the same parent). No `fork` field. |
25
+ | A later step waits for two or more earlier steps | That stage `needs` an array of those parent ids (length ≥ 2). |
25
26
  | Exactly one branch runs | Deciding stage gets `fork: { select: one }`. Each branch `needs` the decider. |
26
27
  | Either, both, or a subset may run | Deciding stage gets `fork: { select: subset }`. Each branch `needs` the decider. |
27
28
 
28
29
  Default `select` is `one` unless the human says more than one branch can run.
29
30
 
30
- Map linear chains, sibling fan-out, and single-level `fork`. A runtime clone count is not knowable from a static description — stay on those three shapes. Leave `clonable` and `clone_forks` unset.
31
+ Map linear chains, sibling fan-out, generic fan-in (`needs` array), and single-level `fork`. A runtime clone count is not knowable from a static description — leave `clonable` and `clone_forks` unset. Clone-list joins (one catalog parent, `priorEnvelopes`) are not this shape.
31
32
 
32
33
  A review, approval, or sign-off step is a gated stage: put `gate_kinds` on that stage file and follow [`stage-prompt-template.md`](stage-prompt-template.md).
33
34
 
@@ -110,6 +111,29 @@ stages:
110
111
 
111
112
  No `fork` field. Both siblings run.
112
113
 
114
+ ### Diamond join
115
+
116
+ "After research and validation both finish, synthesize":
117
+
118
+ ```yaml
119
+ stages:
120
+ - id: clarify
121
+ uses: ./clarify.yaml
122
+ - id: research
123
+ uses: ./research.yaml
124
+ needs: clarify
125
+ - id: validation
126
+ uses: ./validation.yaml
127
+ needs: clarify
128
+ - id: synthesize
129
+ uses: ./synthesize.yaml
130
+ needs:
131
+ - research
132
+ - validation
133
+ ```
134
+
135
+ `needs` array length is ≥ 2. The join reads `priorEnvelopesByStage`, not clone-list `priorEnvelopes`.
136
+
113
137
  ### Fork, select subset
114
138
 
115
139
  "Email, post, or both could go out":
@@ -20,7 +20,7 @@ One directory (or the project root) holds:
20
20
  | `<pipeline-id>.pipeline.yaml` | `id` matching the filename stem; `stages:` object entries |
21
21
  | `<stage-id>.yaml` | `id`, `system_prompt`, `model`; `gate_kinds` when the step is gated |
22
22
 
23
- Each pipeline stage entry has `id` and `uses: ./<id>.yaml` (path relative to the pipeline file). Non-root stages add `needs: <parent-id>`. A deciding stage adds `fork:`. Filename stem matches `id` on every file.
23
+ Each pipeline stage entry has `id` and `uses: ./<id>.yaml` (path relative to the pipeline file). Non-root stages add `needs: <parent-id>`, or `needs:` as an array of at least two parent ids (strings or `{ id, on }`) when the stage waits for several parents. A deciding stage adds `fork:`. Filename stem matches `id` on every file.
24
24
 
25
25
  ## Collisions
26
26
 
@@ -2,19 +2,20 @@
2
2
  name: stageflow-run
3
3
  description: >-
4
4
  Starts a catalog pipeline from the harness, reports progress, and answers
5
- every HITL gate in this chat. Triggers: run a pipeline, start <pipeline>,
6
- check on my run, answer the pending question.
5
+ every HITL gate on the host native question UI when it can, otherwise in
6
+ this chat. Triggers: run a pipeline, start <pipeline>, check on my run,
7
+ answer the pending question.
7
8
  compatibility: Requires the sf CLI on PATH. An MCP host (sf ui or sf mcp) is optional.
8
9
  disable-model-invocation: true
9
10
  ---
10
11
 
11
12
  # Stageflow run
12
13
 
13
- Start a catalog pipeline, keep every HITL gate in this chat, and report the outcome. Author and session-capture own catalog YAML; this job owns the run and any throwaway `*.task.yaml`.
14
+ Start a catalog pipeline, present HITL on the host native question UI when it can, and report the outcome. Author and session-capture own catalog YAML; this job owns the run and any throwaway `*.task.yaml`.
14
15
 
15
16
  Talking jobs cite [`../stageflow/references/control-surface.md`](../stageflow/references/control-surface.md). Probe with [`../stageflow/scripts/detect-host.mjs`](../stageflow/scripts/detect-host.mjs) only. Do not write a second probe.
16
17
 
17
- MCP tool shapes: [`docs/mcp.md`](../../docs/mcp.md). CLI flags and exit codes: [`docs/cli-reference.md`](../../docs/cli-reference.md). Direct tool calls: [`references/mcp-call.md`](references/mcp-call.md). Selection: [`references/task-and-pipeline-selection.md`](references/task-and-pipeline-selection.md).
18
+ MCP tool shapes: [`docs/mcp.md`](../../docs/mcp.md). CLI flags and exit codes: [`docs/cli-reference.md`](../../docs/cli-reference.md). Direct tool calls: [`references/mcp-call.md`](references/mcp-call.md). Selection: [`references/task-and-pipeline-selection.md`](references/task-and-pipeline-selection.md). Gate presentation: [`references/native-question-ui.md`](references/native-question-ui.md).
18
19
 
19
20
  ## Preconditions
20
21
 
@@ -38,15 +39,15 @@ node ../stageflow/scripts/detect-host.mjs --base-url http://127.0.0.1:3847
38
39
 
39
40
  Read [`references/task-and-pipeline-selection.md`](references/task-and-pipeline-selection.md). Follow it until you have a pipeline filesystem path and a task (catalog path, MCP inline object, or CLI throwaway file).
40
41
 
41
- If this chat already has a `runId` and the request is check, answer, or continue: skip start. Host up → [Wait](#wait). Host down and the human wants to answer → [Bridge](#bridge).
42
+ If this chat already has a `runId` and the request is check, answer, or continue: skip start. Host up → [Wait](#wait). Host down → [CLI wait](#cli-wait).
42
43
 
43
44
  **Done when** the target is named, or a live `runId` is in hand.
44
45
 
45
46
  ## MCP tools
46
47
 
47
- Prefer this harness's native Stageflow MCP tools when their names are already in the tool list. When they are not, call [`scripts/mcp-call.mjs`](scripts/mcp-call.mjs) — see [`references/mcp-call.md`](references/mcp-call.md). Always use `mcp-call.mjs --stateless` for a bridge this skill started.
48
+ Prefer this harness's native Stageflow MCP tools when their names are already in the tool list. When they are not, call [`scripts/mcp-call.mjs`](scripts/mcp-call.mjs) — see [`references/mcp-call.md`](references/mcp-call.md). Use `--stateless` only when the **user** started the host with `--mcp-stateless`. Do not start `sf mcp` from this skill.
48
49
 
49
- Call only `list_pipelines`, `list_tasks`, `start_run`, `get_run`, `wait_run`, `list_waiting`, `answer_gate`, `get_health`.
50
+ Call only these Stageflow MCP tools: `list_pipelines`, `list_tasks`, `start_run`, `get_run`, `wait_run`, `list_waiting`, `answer_gate`, `get_health`. Host question tools already in this harness's tool list (`AskQuestion`, `AskUserQuestion`, `ask_user`) are for [Gate](#gate) presentation, not Stageflow MCP.
50
51
 
51
52
  ## MCP path
52
53
 
@@ -78,9 +79,14 @@ Call only `list_pipelines`, `list_tasks`, `start_run`, `get_run`, `wait_run`, `l
78
79
 
79
80
  ### Gate
80
81
 
82
+ Read [`references/native-question-ui.md`](references/native-question-ui.md) before presenting a pending prompt.
83
+
81
84
  1. `list_waiting` with `{ "runId" }`.
82
85
  2. Print the pending prompt text **verbatim** (and artifacts / sub-questions when present).
83
- 3. Collect the human's reply in this chat. Map it to the prompt `kind`:
86
+ 3. Present the decision as that reference directs:
87
+ - If a host question tool is already in this harness's tool list and the gate is representable, invoke that picker. For `multi_question`, one picker call with one question per sub-item when **every** sub-question is representable, then one `answer_gate`; otherwise the **whole** gate in this chat.
88
+ - Otherwise collect the reply in this chat.
89
+ 4. Map the reply to the prompt `kind` (picker Accept/Reject → `accept`/`reject`):
84
90
 
85
91
  | kind | `answer` |
86
92
  |---|---|
@@ -89,12 +95,12 @@ Call only `list_pipelines`, `list_tasks`, `start_run`, `get_run`, `wait_run`, `l
89
95
  | `artifact_backed` | `{ "promptId", "kind": "artifact_backed", "decision": "accept" \| "reject" }` |
90
96
  | `multi_question` | `{ "promptId", "kind": "multi_question", "answers": { "<id>": { "kind", "text" \| "decision" } } }` |
91
97
 
92
- `promptId` is `pending_prompt.id` or `waiting_prompt_id`. Map yes/y/accept/approve to `accept`; no/n/reject/deny to `reject`. Ask once when the kind is `multi_question` and any sub-question is unanswered, or when confirm/artifact_backed is not a clear decision.
98
+ `promptId` is `pending_prompt.id` or `waiting_prompt_id`. Map yes/y/accept/approve/Accept to `accept`; no/n/reject/deny/Reject to `reject`. Each `multi_question` sub-answer keeps that sub's `kind` (`confirm` → `decision`, `free_text` → `text`). Collect every sub-answer before `answer_gate`.
93
99
 
94
- 4. `answer_gate` with `{ "runId", "stageId", "answer" }`.
95
- 5. Return to [Wait](#wait).
100
+ 5. `answer_gate` with `{ "runId", "stageId", "answer" }`.
101
+ 6. Return to [Wait](#wait).
96
102
 
97
- **Done when** `answer_gate` returns `{ "ok": true }` and Wait is re-entered. On `isError` (400 / 404 / 409), print the payload and ask for a corrected reply — do not open another surface.
103
+ **Done when** `answer_gate` returns `{ "ok": true }` and Wait is re-entered. On `isError` (400 / 404 / 409), print the payload and collect a corrected reply the same way (picker if still representable; otherwise this chat).
98
104
 
99
105
  ## CLI path
100
106
 
@@ -113,24 +119,49 @@ Parse the single JSON document.
113
119
  | `0` | `succeeded` | [Report](#report) |
114
120
  | `1` | `failed` | [Report](#report) `reason` verbatim |
115
121
  | `1` | `busy` | [Report](#report) `busy_capacity` / `busy_checkout` and the included fields. Stop. |
116
- | `2` | `waiting` | [Bridge](#bridge) with this `runId` / `runDir` |
122
+ | `2` | `waiting` | [CLI wait](#cli-wait) with this `runId` / `runDir` |
123
+
124
+ **Done when** the outcome is reported, or a waiting exit has handed `runId` to CLI wait.
125
+
126
+ ## CLI wait
127
+
128
+ Host down after `sf run` exit `2`, or when this chat already has a `runId` and the host is down. Do not start `sf mcp`. Presentation follows [`references/native-question-ui.md`](references/native-question-ui.md). Submit with `sf runs answer --json`.
129
+
130
+ 1. Probe again with [`../stageflow/scripts/detect-host.mjs`](../stageflow/scripts/detect-host.mjs) (the host may have appeared). Probe before each `sf runs answer`.
131
+ 2. **Up:** use that host — native tools if present, otherwise `mcp-call.mjs` (omit `--stateless` unless the user started the host with `--mcp-stateless`). Continue at [Gate](#gate).
132
+ 3. **Down:**
117
133
 
118
- **Done when** the outcome is reported, or a waiting exit has handed `runId` and `runDir` to Bridge.
134
+ ```
135
+ sf runs waiting --run <runId> --json
136
+ ```
137
+
138
+ Print the pending prompt **verbatim**. Present it as native-question-ui directs (picker or chat). Map the reply with the [Gate](#gate) kind table.
119
139
 
120
- ## Bridge
140
+ ```
141
+ sf runs answer --run <runId> --stage <stageId> --answer '<json>' --json
142
+ ```
143
+
144
+ Success is `{ "ok": true }` exit `0` even if the run parks again. Do not treat answer as terminal.
145
+
146
+ ```
147
+ sf runs wait --run <runId> --json --until any
148
+ ```
149
+
150
+ A shorter `--timeout-ms` is fine when the harness timeout is tight. Default `60000`, max `240000`. Branch on JSON `reason`, not wait exit `0`:
151
+
152
+ | `reason` | next |
153
+ |---|---|
154
+ | `waiting` or `already` with a waiting snapshot | `sf runs waiting` then answer |
155
+ | `terminal` | [Report](#report) |
156
+ | `timeout` | call `sf runs wait` again |
121
157
 
122
- A CLI waiting exit has no answer command. Keep the gate in this chat by joining a host on the same project root (git top-level when `git rev-parse --show-toplevel` succeeds, otherwise the current directory).
158
+ Wait abort (exit `130`, `{ "error", "code": "aborted" }`) → [Report](#report): the run continues, resumable later. Stop. Do not reuse `sf run` exit `2` for wait.
123
159
 
124
- 1. Probe again with [`../stageflow/scripts/detect-host.mjs`](../stageflow/scripts/detect-host.mjs) (the host may have appeared).
125
- 2. **Up:** use that host — native tools if present, otherwise `mcp-call.mjs` (omit `--stateless`). Continue at [Gate](#gate).
126
- 3. **Down:** start `sf mcp --mcp-stateless` as a background process on that same project root. This is a disposable HITL bridge, not setup auto-start.
127
- 4. Probe until stdout is `up <baseUrl>`, at most 8 times. **Done when** the host is up. If it stays down, [Report](#report) the parked `runId` / `runDir` and the bind/stderr text. Stop.
128
- 5. Drive the rest with `mcp-call.mjs --stateless` only: [Gate](#gate) → [Wait](#wait) until terminal. A second wait on the same run reuses this process.
129
- 6. When the run is terminal, stop the bridge process.
160
+ 4. If `sf runs answer` refuses because the host came up, continue that gate via MCP [Gate](#gate). Do not dual-write.
130
161
 
131
162
  If this chat ends before terminal, say the run stays in `.stageflow/` and to continue by re-invoking this skill (or starting a host and using MCP).
132
163
 
133
- **Done when** the run is terminal and the bridge this skill started is stopped, or a parked-run failure is reported.
164
+ **Done when** the run is terminal, aborted, or a hard error is printed.
134
165
 
135
166
  ## Report
136
167
 
@@ -142,10 +173,10 @@ Print one shape on every path:
142
173
  4. Run id.
143
174
  5. Run folder (`runDir` / `.stageflow/`).
144
175
 
145
- MCP `get_run` / `wait_run` carry live stage state. `sf run --json` reports start and finish only — say that in chat when the CLI path ran without a host. A succeeded report names id and folder and does not keep waiting language. A failure names `reason` in this same shape.
176
+ MCP `get_run` / `wait_run` and `sf runs wait` / `sf runs show` `--json` carry live stage state. `sf run --json` reports start and finish only — say that in chat when the CLI path ran without a later wait/show. A succeeded report names id and folder and does not keep waiting language. A failure names `reason` in this same shape.
146
177
 
147
178
  **Done when** that five-part report is printed.
148
179
 
149
180
  ## Non-goals
150
181
 
151
- This job starts, watches, and answers runs. It does not require `sf ui`. The operator console is not the answer path. It does not register or invent an MCP tool.
182
+ This job starts, watches, and answers runs. It does not require `sf ui`. The operator console is not the answer path. It does not register or invent an MCP tool. It does not start `sf mcp`. It does not retry, abandon, or rerun stages.
@@ -1,6 +1,6 @@
1
1
  # mcp-call
2
2
 
3
- Use [`../scripts/mcp-call.mjs`](../scripts/mcp-call.mjs) when this harness has no native Stageflow MCP tools, and always for a self-started `sf mcp --mcp-stateless` bridge.
3
+ Use [`../scripts/mcp-call.mjs`](../scripts/mcp-call.mjs) when the Stageflow host is up and this harness has no native Stageflow MCP tools. Do not start `sf mcp` from this skill.
4
4
 
5
5
  ```
6
6
  node scripts/mcp-call.mjs --base-url <url> --tool <name> --args '<json>' [--stateless]
@@ -8,7 +8,7 @@ node scripts/mcp-call.mjs --base-url <url> --tool <name> --args '<json>' [--stat
8
8
 
9
9
  Default `--base-url` is `http://127.0.0.1:3847`. `--args` defaults to `{}`.
10
10
 
11
- `--stateless` sends one `tools/call` and no session header. Use it for hosts started with `sf mcp --mcp-stateless`.
11
+ `--stateless` sends one `tools/call` and no session header. Use it for hosts the **user** started with `sf mcp --mcp-stateless`, not a skill-started process.
12
12
 
13
13
  Without `--stateless`: `initialize`, capture `Mcp-Session-Id`, reuse it for `tools/call`.
14
14
 
@@ -0,0 +1,78 @@
1
+ # Native question UI
2
+
3
+ Present a waiting HITL gate on the coding-agent host's structured question tool when that tool can represent the gate. Otherwise collect the reply in this chat. Submit with `answer_gate` when the Stageflow host is up, or `sf runs answer --json` when it is down. This file is presentation only — it does not change MCP tools, `detect-host.mjs`, or the operator console.
4
+
5
+ Read `pending_prompt` from `list_waiting` or `sf runs waiting --json` (not only `waiting_questions` strings).
6
+
7
+ ## Detect a picker
8
+
9
+ Inspect **this harness's current tool list**. A picker is a tool that collects a structured choice (question text plus labeled options), not a free-form chat reply.
10
+
11
+ Do not run `detect-host.mjs` for this. That script is Stageflow host up/down (MCP vs CLI). It does not detect a question UI.
12
+
13
+ | If this name is in the tool list | Use it |
14
+ |---|---|
15
+ | `AskQuestion` | Cursor question cards |
16
+ | `AskUserQuestion` | Claude Code questions |
17
+ | `ask_user` | Pi questions |
18
+
19
+ If none of those (or another options-based question tool already in the list) is present, collect in chat. Do not invent a tool name or call shape. Codex and OpenCode stay in chat until an options-based question tool appears in the list.
20
+
21
+ Pass the pending message as the question text and the mapped labels as that tool's options. One question per call except `multi_question`: one call with one question per representable sub-item when the tool accepts a questions array (Cursor `AskQuestion` does). Do not invent placeholder options such as Type in Other — host Other is the custom-text escape. If the picker returns a custom or Other value, map that text the same as a chat reply.
22
+
23
+ **Done when** you know picker or chat for this gate.
24
+
25
+ ## Representable vs chat
26
+
27
+ | Gate | Picker | Chat |
28
+ |---|---|---|
29
+ | `confirm` | Accept and Reject | Host has no picker |
30
+ | `artifact_backed` | Accept and Reject (decision only) | Host has no picker |
31
+ | `free_text` with a harvested closed set | Those names as options | Open-ended, or harvest is ambiguous |
32
+ | `free_text` with no closed set | — | Always |
33
+ | `multi_question` | One picker call with one question per sub-item when **every** sub-question is representable; then one submit | Any sub-question is open or unmappable — the **whole** gate in chat |
34
+
35
+ Map picker Accept / yes / approve → `accept`. Reject / no / deny → `reject`. Always include `promptId` (`pending_prompt.id` or `waiting_prompt_id`).
36
+
37
+ | Item | Submit |
38
+ |---|---|
39
+ | Top-level or sub `confirm` | `{ "kind": "confirm", "decision": "accept" \| "reject" }` |
40
+ | Top-level `artifact_backed` | `{ "kind": "artifact_backed", "decision": "accept" \| "reject" }` plus optional `text` after a reject follow-up |
41
+ | Top-level or sub `free_text` (including harvested) | `{ "kind": "free_text", "text": "<selected label>" }` |
42
+
43
+ Never submit a `confirm` item as `free_text`. For `multi_question`, wrap sub-answers in the Gate `answers` object and submit once (`answer_gate` or `sf runs answer --json`).
44
+
45
+ When in doubt, chat.
46
+
47
+ **Done when** the path (picker or chat) is chosen from this table.
48
+
49
+ ## Harvest
50
+
51
+ Harvest only from `pending_prompt.message` (or a `multi_question` item `message`) when it names a **hard closed set**:
52
+
53
+ - `reply with exactly one of:`
54
+ - `choose one of:`
55
+ - `exactly one of`
56
+
57
+ Split the names after that directive (commas, `or`, newlines). Those literal names are the picker options.
58
+
59
+ Do **not** harvest loose example lists (`you might try`, `e.g.`, `for example` without `exactly one of`). A listed example set that is not a hard closed directive stays in chat.
60
+
61
+ Submit the selected label as `text` for `free_text` items. If the prompt also lists aliases for the same choice (for example `minimal` and `prototype~1`), keep every listed name on the card; do not rewrite the label at submit — the stage maps synonyms.
62
+
63
+ **Done when** the option list is the prompt's literal closed set, or the gate stayed in chat.
64
+
65
+ ## Artifact-backed
66
+
67
+ 1. Print the pending message and artifact paths (from `list_waiting`) in this chat. Do not put artifact bodies on the card. Do not call `read_artifact` unless the human asked to open a file.
68
+ 2. Open the picker with Accept and Reject.
69
+ 3. Accept → submit immediately (`decision: "accept"`).
70
+ 4. Reject → one chat follow-up for optional notes, then submit `decision: "reject"` and `text` when they typed notes.
71
+
72
+ **Done when** the operator has seen the paths in chat and the decision is on the picker (or chat if there is no picker).
73
+
74
+ ## Errors
75
+
76
+ On MCP `answer_gate` `isError` (400 / 404 / 409) or CLI `sf runs answer` exit `1`, print the payload. Collect a corrected reply on the picker if the gate is still representable; otherwise chat. Then submit again.
77
+
78
+ **Done when** `{ "ok": true }` or the error payload has been printed and a corrected reply is in hand.