stageflow 0.2.0 → 0.8.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.
- package/README.md +179 -14
- package/dist/agent/activity.d.ts +12 -1
- package/dist/agent/activity.js +37 -2
- package/dist/agent/activityObserver.d.ts +5 -1
- package/dist/agent/activityObserver.js +174 -11
- package/dist/agent/cursorProvider.d.ts +1 -10
- package/dist/agent/cursorProvider.js +18 -10
- package/dist/agent/fakeAgent.js +15 -7
- package/dist/agent/piAdapter.d.ts +18 -1
- package/dist/agent/piAdapter.js +115 -45
- package/dist/agent/port.d.ts +7 -0
- package/dist/agent/port.js +3 -0
- package/dist/agent/providerAuth.js +12 -7
- package/dist/catalog/displayCatalogPath.d.ts +12 -0
- package/dist/catalog/displayCatalogPath.js +28 -0
- package/dist/cli/artifactCommand.d.ts +10 -0
- package/dist/cli/artifactCommand.js +120 -0
- package/dist/cli/envelopeCommand.d.ts +32 -0
- package/dist/cli/envelopeCommand.js +285 -0
- package/dist/cli/exportRunCommand.d.ts +10 -0
- package/dist/cli/exportRunCommand.js +150 -0
- package/dist/cli/handoffFormat.d.ts +22 -0
- package/dist/cli/handoffFormat.js +61 -0
- package/dist/cli/initCommand.d.ts +9 -0
- package/dist/cli/initCommand.js +71 -0
- package/dist/cli/initTemplates.d.ts +3 -0
- package/dist/cli/initTemplates.js +19 -0
- package/dist/cli/operatorCatalog.d.ts +10 -0
- package/dist/cli/operatorCatalog.js +16 -0
- package/dist/cli/runCommand.d.ts +7 -1
- package/dist/cli/runCommand.js +83 -11
- package/dist/cli/runOutput.d.ts +4 -1
- package/dist/cli/runOutput.js +36 -8
- package/dist/cli/skillsCommand.d.ts +16 -0
- package/dist/cli/skillsCommand.js +459 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -22
- package/dist/config/browseCatalog.d.ts +45 -0
- package/dist/config/browseCatalog.js +170 -0
- package/dist/config/createPipeline.d.ts +12 -3
- package/dist/config/createPipeline.js +292 -100
- package/dist/config/createStage.d.ts +10 -4
- package/dist/config/createStage.js +44 -14
- package/dist/config/listConfig.d.ts +14 -19
- package/dist/config/listConfig.js +26 -191
- package/dist/config/listModelsFromManifest.d.ts +2 -0
- package/dist/config/listModelsFromManifest.js +4 -0
- package/dist/config/loadOutcome.d.ts +4 -0
- package/dist/config/loadPipeline.d.ts +1 -15
- package/dist/config/loadPipeline.js +88 -142
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +60 -32
- package/dist/config/loadStageflowManifest.d.ts +5 -0
- package/dist/config/loadStageflowManifest.js +157 -0
- package/dist/config/mergePipelineIncludes.d.ts +9 -0
- package/dist/config/mergePipelineIncludes.js +141 -0
- package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
- package/dist/config/normalizePipelineStageEntry.js +210 -0
- package/dist/config/pipelineStageKeys.d.ts +4 -0
- package/dist/config/pipelineStageKeys.js +14 -0
- package/dist/config/resolveCatalogContext.d.ts +13 -0
- package/dist/config/resolveCatalogContext.js +13 -0
- package/dist/config/resolveForkEmitContext.d.ts +4 -0
- package/dist/config/resolveForkEmitContext.js +34 -0
- package/dist/config/resolvePipelineDag.d.ts +5 -1
- package/dist/config/resolvePipelineDag.js +86 -36
- package/dist/config/scanCatalogPaths.d.ts +4 -0
- package/dist/config/scanCatalogPaths.js +83 -0
- package/dist/config/validateCatalog.d.ts +22 -4
- package/dist/config/validateCatalog.js +169 -132
- package/dist/envelope/check.d.ts +2 -0
- package/dist/envelope/check.js +58 -0
- package/dist/envelope/cloneForks.d.ts +2 -0
- package/dist/envelope/cloneForks.js +62 -0
- package/dist/envelope/forkChoice.d.ts +3 -0
- package/dist/envelope/forkChoice.js +47 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mcp/catalogTools.d.ts +3 -0
- package/dist/mcp/catalogTools.js +187 -0
- package/dist/mcp/controlTools.d.ts +3 -0
- package/dist/mcp/controlTools.js +147 -0
- package/dist/mcp/deps.d.ts +11 -0
- package/dist/mcp/deps.js +1 -0
- package/dist/mcp/projectRun.d.ts +1 -37
- package/dist/mcp/projectRun.js +1 -57
- package/dist/mcp/resources.d.ts +5 -0
- package/dist/mcp/resources.js +45 -0
- package/dist/mcp/server.d.ts +16 -3
- package/dist/mcp/server.js +159 -3
- package/dist/mcp/toolResults.d.ts +7 -0
- package/dist/mcp/toolResults.js +6 -0
- package/dist/mcp/tools.d.ts +2 -7
- package/dist/mcp/tools.js +52 -86
- package/dist/mcp/waitRun.d.ts +46 -0
- package/dist/mcp/waitRun.js +146 -0
- package/dist/mcp/waitingGates.d.ts +12 -0
- package/dist/mcp/waitingGates.js +83 -0
- package/dist/project/findProjectRoot.d.ts +2 -0
- package/dist/project/findProjectRoot.js +51 -0
- package/dist/project/globalHome.d.ts +2 -0
- package/dist/project/globalHome.js +19 -0
- package/dist/project/resolveProjectContext.d.ts +9 -0
- package/dist/project/resolveProjectContext.js +38 -0
- package/dist/project/resolveStageflowContext.d.ts +11 -0
- package/dist/project/resolveStageflowContext.js +66 -0
- package/dist/projection/projectRun.d.ts +44 -0
- package/dist/projection/projectRun.js +71 -0
- package/dist/prompt/priorEnvelope.d.ts +1 -1
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +4 -0
- package/dist/runstore/paths.js +2 -0
- package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
- package/dist/runstore/pipelineDagSnapshot.js +90 -1
- package/dist/runstore/port.d.ts +22 -2
- package/dist/runstore/port.js +4 -1
- package/dist/runstore/runProjection.d.ts +1 -1
- package/dist/runstore/runProjection.js +25 -4
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
- package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +4 -1
- package/dist/runstore/stageInstanceId.d.ts +4 -0
- package/dist/runstore/stageInstanceId.js +23 -0
- package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
- package/dist/runstore/syntheticStageSnapshot.js +2 -1
- package/dist/runstore/trackProjection.js +28 -12
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +1 -1
- package/dist/runtime/cloneSchedule.d.ts +18 -0
- package/dist/runtime/cloneSchedule.js +233 -0
- package/dist/runtime/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/envelopeRouting.d.ts +2 -1
- package/dist/runtime/envelopeRouting.js +80 -13
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +15 -1
- package/dist/runtime/pipelineScheduler.d.ts +8 -2
- package/dist/runtime/pipelineScheduler.js +215 -39
- package/dist/runtime/reloadRunCatalog.d.ts +5 -0
- package/dist/runtime/reloadRunCatalog.js +44 -0
- package/dist/runtime/resumeReconstruct.d.ts +1 -0
- package/dist/runtime/resumeReconstruct.js +31 -10
- package/dist/runtime/runChangeBus.d.ts +18 -0
- package/dist/runtime/runChangeBus.js +83 -0
- package/dist/runtime/runManager.d.ts +4 -0
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +12 -0
- package/dist/runtime/settingsFile.js +87 -6
- package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
- package/dist/runtime/stageAttemptBootstrap.js +18 -7
- package/dist/runtime/stageProcessLauncher.js +3 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageRunner.d.ts +1 -0
- package/dist/runtime/stageRunner.js +17 -15
- package/dist/runtime/stageWorker.js +13 -10
- package/dist/server/bootstrap.d.ts +32 -0
- package/dist/server/bootstrap.js +56 -0
- package/dist/server/createHttpHost.d.ts +34 -0
- package/dist/server/createHttpHost.js +70 -0
- package/dist/server/http.d.ts +6 -13
- package/dist/server/http.js +297 -352
- package/dist/server/mcpHost.d.ts +22 -0
- package/dist/server/mcpHost.js +21 -0
- package/dist/server/operatorResults.d.ts +13 -0
- package/dist/server/operatorResults.js +22 -0
- package/dist/tools/emitStageEnvelope.d.ts +12 -1
- package/dist/tools/emitStageEnvelope.js +27 -1
- package/dist/types/envelope.d.ts +3 -0
- package/dist/types/forkChoice.d.ts +30 -0
- package/dist/types/forkChoice.js +1 -0
- package/dist/types/pipeline.d.ts +59 -2
- package/dist/types/stageflowManifest.d.ts +23 -0
- package/dist/types/stageflowManifest.js +1 -0
- package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
- package/dist/ui/assets/index-CSoGDA3A.js +118 -0
- package/dist/ui/index.html +3 -2
- package/dist/ui/stageflow-icon.svg +12 -0
- package/package.json +8 -5
- package/skills/install-suite.sh +107 -0
- package/skills/stageflow/SKILL.md +26 -0
- package/skills/stageflow/references/control-surface.md +26 -0
- package/skills/stageflow/scripts/detect-host.mjs +54 -0
- package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
- package/skills/stageflow-author/SKILL.md +58 -0
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
- package/skills/stageflow-author/references/catalog-mapping.md +131 -0
- package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
- package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
- package/skills/stageflow-author/references/validate-and-report.md +38 -0
- package/skills/stageflow-delegate/SKILL.md +18 -0
- package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
- package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
- package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
- package/skills/stageflow-run/SKILL.md +151 -0
- package/skills/stageflow-run/references/mcp-call.md +17 -0
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
- package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
- package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
- package/skills/stageflow-session-capture/SKILL.md +94 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
- package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
- package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
- package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
- package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
- package/skills/stageflow-setup/SKILL.md +110 -0
- package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
package/README.md
CHANGED
|
@@ -1,12 +1,55 @@
|
|
|
1
|
-
# Stageflow
|
|
1
|
+
# <img src="ui/public/stageflow-icon.svg" alt="" width="44" height="44" valign="middle"> Stageflow
|
|
2
2
|
|
|
3
|
-
CLI pipeline runtime for configurable
|
|
3
|
+
CLI pipeline runtime for **configurable stages** on [Pi](https://github.com/badlogic/pi-mono), with a local operator console.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/stageflow)
|
|
6
|
+
[](https://www.npmjs.com/package/stageflow)
|
|
7
|
+

|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://github.com/tejasghutukade/stageflow/issues)
|
|
10
|
+
[](https://github.com/tejasghutukade/stageflow/actions/workflows/ci.yml)
|
|
11
|
+
|
|
12
|
+
Author pipeline-owned YAML in your project. Stageflow runs each stage in a fresh Pi agent session, writes a structured handoff envelope, and keeps run state under `.stageflow/`. Bins: **`sf`** and **`stageflow`**.
|
|
13
|
+
|
|
14
|
+
## Why Stageflow?
|
|
15
|
+
|
|
16
|
+
Multi-step agent work breaks down when every handoff is ad hoc — a shell script here, a chat transcript there, no shared contract between steps. You end up re-explaining context, losing artifacts, and unable to run the same flow locally and in CI.
|
|
17
|
+
|
|
18
|
+
Stageflow treats **stages as the unit of composition**. You author pipeline-owned YAML — `*.pipeline.yaml`, `*.task.yaml`, optional `stageflow.yaml` manifest — and define whatever workflow fits your domain: release automation, research pipelines, content review, SDLC, ops runbooks, or something entirely custom. Each stage runs in a **fresh Pi session**, emits a typed **envelope** for the next stage, and can pause on **human-in-the-loop (HITL)** gates when you need an operator in the loop.
|
|
19
|
+
|
|
20
|
+
The same pipeline runs three ways without rewriting anything:
|
|
21
|
+
|
|
22
|
+
- **Locally** — `sf ui` for triage, provider setup, and gate replies
|
|
23
|
+
- **Headless / CI** — `sf validate` and `sf run --json` with predictable exit codes
|
|
24
|
+
- **Via MCP** — Streamable HTTP tools when the console is running
|
|
25
|
+
|
|
26
|
+
**Stageflow is not an SDLC tool.** Software delivery is a popular pattern in fixtures and dogfood flows, but stages are user-authored and domain-agnostic. If you can express a multi-step workflow in YAML, Stageflow can run it on Pi.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Pipeline-owned YAML** — `*.pipeline.yaml` with inline stages or `uses:` refs; separate `*.task.yaml` files; optional repo-root `stageflow.yaml` manifest
|
|
31
|
+
- **Path-based CLI** — `--pipeline` and `--task` take filesystem paths (no bare-id lookup)
|
|
32
|
+
- **Pi-native** — runs on `@earendil-works/pi-coding-agent`; reuse an existing Pi login (`pi_home`) or store credentials in `~/.stageflow/agent/auth.json` (`sf_owned`)
|
|
33
|
+
- **Envelope handoffs** — typed stage payloads and artifacts via `write_stage_artifact` / `emit_stage_envelope`
|
|
34
|
+
- **HITL gates** — operator questions in the console; CI exits `2` when a run is waiting
|
|
35
|
+
- **Operator console** — triage runs, connect providers, answer gates, inspect transcripts at `http://127.0.0.1:3847`
|
|
36
|
+
- **MCP endpoint** — Streamable HTTP at `/mcp` when `sf ui` is running
|
|
37
|
+
- **CI / headless** — `sf validate --strict --json`, `sf run --json` with exit codes `0` / `1` / `2`
|
|
38
|
+
- **Parallel stages** — pipeline DAG with fan-out and join (see [YAML catalog](docs/yaml-catalog.md))
|
|
39
|
+
- **Clonable fan-out** — clone one successor N times at completion, then join (see [YAML catalog](docs/yaml-catalog.md#clonable-successors))
|
|
40
|
+
- **SQLite run store** — `<git-root>/.stageflow/` state plus per-run workspaces under `.stageflow/runs/`
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
6
43
|
|
|
7
44
|
Requires **Node.js ≥ 20**.
|
|
8
45
|
|
|
9
|
-
|
|
46
|
+
**Quick install (macOS / Linux):**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
curl -fsSL https://raw.githubusercontent.com/tejasghutukade/stageflow/main/install.sh | bash
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**npm:**
|
|
10
53
|
|
|
11
54
|
```bash
|
|
12
55
|
npm i -g stageflow
|
|
@@ -18,20 +61,77 @@ npm i -g ./stageflow-*.tgz
|
|
|
18
61
|
|
|
19
62
|
`better-sqlite3` ships prebuilds for common platforms. `--ignore-scripts` is fine when a prebuild exists. Benign `node-gyp` warnings during install can be ignored if `require("better-sqlite3")` works.
|
|
20
63
|
|
|
64
|
+
**Harness skills** (Cursor, Claude Code, Codex, Pi, OpenCode) — from a consumer project:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx skills add tejasghutukade/stageflow
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then ask the agent to set up Stageflow. Details: [docs/skills-suite.md](docs/skills-suite.md).
|
|
71
|
+
|
|
21
72
|
## Quick start
|
|
22
73
|
|
|
23
|
-
In a project directory
|
|
74
|
+
In a project directory (preferably a git repo):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
sf init
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This scaffolds `stageflow.yaml`, `pipelines/hello.pipeline.yaml` (inline stage), and `tasks/hello.task.yaml`.
|
|
24
81
|
|
|
25
|
-
|
|
26
|
-
- `stages/` — stage definitions (instructions, model, optional human gates)
|
|
27
|
-
- `tasks/` — work items (goal, pipeline, optional checkout)
|
|
82
|
+
Run (after connecting a provider — see below):
|
|
28
83
|
|
|
29
84
|
```bash
|
|
30
85
|
sf ui # operator console at http://127.0.0.1:3847
|
|
31
|
-
sf run --
|
|
86
|
+
sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Expanded walkthrough: [docs/quickstart.md](docs/quickstart.md)
|
|
90
|
+
|
|
91
|
+
## Connect a model provider
|
|
92
|
+
|
|
93
|
+
Each stage sets a **`model`** id in YAML (e.g. `anthropic/claude-sonnet-4-5`). The matching provider must be authenticated before runs succeed — `sf validate` does not check auth.
|
|
94
|
+
|
|
95
|
+
**Operator console** (easiest for local setup):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
sf ui
|
|
32
99
|
```
|
|
33
100
|
|
|
34
|
-
|
|
101
|
+
Open **Settings → Providers** (or **Connect** from the rail when nothing is configured) and sign in with API key or OAuth.
|
|
102
|
+
|
|
103
|
+
**CLI** (works headless and in CI):
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
sf providers list
|
|
107
|
+
sf providers login anthropic --type api_key
|
|
108
|
+
sf providers login anthropic --type api_key --api-key-env ANTHROPIC_API_KEY
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use `sf providers list` to see provider ids and supported auth types (`api_key`, `oauth`).
|
|
112
|
+
|
|
113
|
+
**Credential storage:** reuse Pi's shared auth file (`pi_home`) or keep credentials in Stageflow's global store (`sf_owned`):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
sf providers detect
|
|
117
|
+
sf providers source set pi_home # or sf_owned
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Stageflow is a thin Pi shell — you do not need Pi CLI `/login` as a hard prerequisite if you configure providers via the console or `sf providers`.
|
|
121
|
+
|
|
122
|
+
Full reference: [docs/providers.md](docs/providers.md)
|
|
123
|
+
|
|
124
|
+
## Operator console
|
|
125
|
+
|
|
126
|
+
Start the console with `sf ui` (default `http://127.0.0.1:3847`).
|
|
127
|
+
|
|
128
|
+
- **Runs** — active and recent pipeline runs, capacity, and status at a glance
|
|
129
|
+
- **Run detail** — stage timeline, transcripts, envelope payloads, and artifact paths
|
|
130
|
+
- **HITL reply** — answer operator gates (`ask_operator`) without leaving the browser
|
|
131
|
+
- **Pipelines** — browse manifest-declared pipeline definitions
|
|
132
|
+
- **Settings → Providers** — connect model providers (`pi_home` or `sf_owned` credential storage)
|
|
133
|
+
|
|
134
|
+
*Screenshot coming soon — capture after console polish lands (see `docs/img/`).*
|
|
35
135
|
|
|
36
136
|
## Headless / CI
|
|
37
137
|
|
|
@@ -50,7 +150,7 @@ sf providers login <providerId> --api-key-env <VAR>
|
|
|
50
150
|
If the provider also supports OAuth, pass `--type api_key`.
|
|
51
151
|
|
|
52
152
|
```bash
|
|
53
|
-
sf run --
|
|
153
|
+
sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml --json
|
|
54
154
|
```
|
|
55
155
|
|
|
56
156
|
The process exits `0` when the Run succeeded, `1` when it failed (including a busy start), and `2` when waiting. `sf run --json` prints one stdout document. `ok` is true only for `succeeded`. Busy has no `runId`.
|
|
@@ -64,13 +164,74 @@ The process exits `0` when the Run succeeded, `1` when it failed (including a bu
|
|
|
64
164
|
|
|
65
165
|
On a mixed Pipeline, default wait parks the Run (exit `2`). `--skip-gates` fails the Stage (exit `1`). A Pipeline with no HITL does not need the flag.
|
|
66
166
|
|
|
167
|
+
Post-run extraction (dogfooded in [Archify PR diagrams](examples/archify-on-pr/)):
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
sf run ... --json --include stages > sf-run.json
|
|
171
|
+
sf envelope get --from sf-run.json --stage author-diagrams --format handoff --json
|
|
172
|
+
sf skills install --from-zip <url> --skill-name archify
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
See [docs/ci.md](docs/ci.md) for the full CI recipe and [`.github/actions/sf-run`](.github/actions/sf-run) composite action.
|
|
176
|
+
|
|
67
177
|
## State
|
|
68
178
|
|
|
69
|
-
Runtime state lives in
|
|
179
|
+
Runtime state lives in **`<git-root>/.stageflow/`** when inside a git repository (SQLite + per-run workspaces under `.stageflow/runs/`). Global config and `sf_owned` auth live under **`~/.stageflow/`**. If `.stageflow` is missing and `.software-factory` exists from an older install, the next store open renames it to `.stageflow` once.
|
|
70
180
|
|
|
71
181
|
## MCP
|
|
72
182
|
|
|
73
|
-
`sf ui` also serves a Streamable HTTP MCP endpoint at `http://127.0.0.1:3847/mcp` (URL printed on boot). Point a Cursor (or other) MCP client at that URL
|
|
183
|
+
`sf ui` also serves a Streamable HTTP MCP endpoint at `http://127.0.0.1:3847/mcp` (URL printed on boot). Point a Cursor (or other) MCP client at that URL.
|
|
184
|
+
|
|
185
|
+
Available tools: `list_pipelines`, `list_tasks`, `list_runs`, `get_health`, `start_run`, `get_run`, `read_artifact`.
|
|
186
|
+
|
|
187
|
+
Full reference: [docs/mcp.md](docs/mcp.md)
|
|
188
|
+
|
|
189
|
+
## Stageflow vs Conductor
|
|
190
|
+
|
|
191
|
+
Both projects address multi-step agent workflows. They differ in orchestration model and runtime.
|
|
192
|
+
|
|
193
|
+
| | **Stageflow** | **Conductor** |
|
|
194
|
+
|---|---------------|---------------|
|
|
195
|
+
| **Model** | Configurable **stages** on Pi; pipeline-owned YAML (any domain) | Multi-**agent** workflow graph |
|
|
196
|
+
| **Orchestration** | Pipeline DAG + stage worker | Jinja routing, no LLM in router |
|
|
197
|
+
| **Unit of work** | Task → Pipeline → Stage attempts | Workflow → Agents |
|
|
198
|
+
| **Handoff** | Typed **envelope** + artifacts | Agent output → context |
|
|
199
|
+
| **Human gates** | Operator console + MCP | Dashboard + TUI fleet |
|
|
200
|
+
| **Runtime** | Node.js, Pi coding agent | Python, Copilot/Claude SDKs |
|
|
201
|
+
| **Best for** | Personal/team **multi-stage Pi workflows** you define (releases, research, SDLC, …) | Enterprise multi-agent workflows |
|
|
202
|
+
|
|
203
|
+
If you want deterministic YAML routing across many agents, look at [Conductor](https://github.com/microsoft/conductor). If you want stage-bound Pi runs with reviewable envelopes and an operator console for **workflows you author**, use Stageflow.
|
|
204
|
+
|
|
205
|
+
## Examples
|
|
206
|
+
|
|
207
|
+
| Example | Description |
|
|
208
|
+
|---------|-------------|
|
|
209
|
+
| **[archify-on-pr](examples/archify-on-pr/)** | **Featured** — PR diagram automation: conditional fork, skill binding, envelope handoff, GHA deliver |
|
|
210
|
+
| [hello-world](examples/hello-world/) | Single stage, domain-neutral |
|
|
211
|
+
| [plan-review](examples/plan-review/) | Multi-stage with operator gate — SDLC-style **example** |
|
|
212
|
+
| [conditional-fork](examples/conditional-fork/) | Exclusive fork routing with operator branch choice |
|
|
213
|
+
| [github-release](examples/github-release/) | Dogfood: draft + publish GitHub Release |
|
|
214
|
+
| [ci-validate](examples/ci-validate/) | Strict validate in CI |
|
|
215
|
+
|
|
216
|
+
Index: [examples/README.md](examples/README.md)
|
|
217
|
+
|
|
218
|
+
## Documentation
|
|
219
|
+
|
|
220
|
+
Full docs: **[tejasghutukade.github.io/stageflow](https://tejasghutukade.github.io/stageflow/)** (GitHub Pages — live after merge to `main` and Pages enabled). Source in [`docs/`](docs/).
|
|
221
|
+
|
|
222
|
+
| Doc | Description |
|
|
223
|
+
|-----|-------------|
|
|
224
|
+
| [docs/README.md](docs/README.md) | Documentation index |
|
|
225
|
+
| [docs/quickstart.md](docs/quickstart.md) | Expanded quick start |
|
|
226
|
+
| [docs/yaml-catalog.md](docs/yaml-catalog.md) | Pipelines, stages, tasks schema |
|
|
227
|
+
| [docs/cli-reference.md](docs/cli-reference.md) | `sf run`, `sf envelope`, `sf skills`, `sf validate`, `sf ui`, `sf providers` |
|
|
228
|
+
| [docs/envelopes.md](docs/envelopes.md) | Handoff envelope contract |
|
|
229
|
+
| [docs/hitl.md](docs/hitl.md) | Gate kinds, `--skip-gates`, exit code `2` |
|
|
230
|
+
| [docs/ci.md](docs/ci.md) | `--json`, env vars, GitHub Actions |
|
|
231
|
+
| [docs/mcp.md](docs/mcp.md) | MCP tool reference |
|
|
232
|
+
| [docs/providers.md](docs/providers.md) | Pi providers, `sf providers` |
|
|
233
|
+
| [docs/operator-console.md](docs/operator-console.md) | Console IA and settings |
|
|
234
|
+
| [docs/compare-conductor.md](docs/compare-conductor.md) | Positioning deep dive |
|
|
74
235
|
|
|
75
236
|
## Develop from source
|
|
76
237
|
|
|
@@ -82,8 +243,12 @@ npm run build && npm run ui:build # ui:build builds the UI and copies assets i
|
|
|
82
243
|
sf ui
|
|
83
244
|
```
|
|
84
245
|
|
|
246
|
+
Run tests: `npm test` and `npm run ui:test`. Typecheck: `npm run typecheck`.
|
|
247
|
+
|
|
85
248
|
## License
|
|
86
249
|
|
|
87
250
|
MIT © Tejas G
|
|
88
251
|
|
|
89
|
-
Issues
|
|
252
|
+
**Support:** [GitHub Issues](https://github.com/tejasghutukade/stageflow/issues) · [SUPPORT.md](SUPPORT.md)
|
|
253
|
+
|
|
254
|
+
**Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)
|
package/dist/agent/activity.d.ts
CHANGED
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* those Q&A bodies.
|
|
12
12
|
*/
|
|
13
13
|
import type { AskOperatorAnswer, AskOperatorPrompt } from "../tools/askOperator.js";
|
|
14
|
-
export declare const ACTIVITY_TEXT_LIMIT =
|
|
14
|
+
export declare const ACTIVITY_TEXT_LIMIT = 2000;
|
|
15
|
+
export declare const ACTIVITY_TEXT_LIMIT_ENV = "STAGEFLOW_ACTIVITY_TEXT_LIMIT";
|
|
16
|
+
export declare const ACTIVITY_VERBOSE_ENV = "STAGEFLOW_ACTIVITY_VERBOSE";
|
|
17
|
+
export declare function readActivityTextLimit(env?: Record<string, string | undefined>, override?: number): number;
|
|
18
|
+
export declare function readActivityVerbose(env?: Record<string, string | undefined>, override?: boolean): boolean;
|
|
15
19
|
export type StageActivityEvent = {
|
|
16
20
|
event: "agent_start";
|
|
17
21
|
} | {
|
|
@@ -29,6 +33,11 @@ export type StageActivityEvent = {
|
|
|
29
33
|
toolCallId?: string;
|
|
30
34
|
isError?: boolean;
|
|
31
35
|
resultPreview?: string;
|
|
36
|
+
} | {
|
|
37
|
+
event: "tool_progress";
|
|
38
|
+
toolName: string;
|
|
39
|
+
toolCallId?: string;
|
|
40
|
+
textPreview?: string;
|
|
32
41
|
} | {
|
|
33
42
|
event: "message";
|
|
34
43
|
role: string;
|
|
@@ -59,6 +68,8 @@ export type StageLifecycleEvent = {
|
|
|
59
68
|
} | {
|
|
60
69
|
event: "failed";
|
|
61
70
|
reason: string;
|
|
71
|
+
} | {
|
|
72
|
+
event: "skipped";
|
|
62
73
|
};
|
|
63
74
|
export type StageLogLine = StageActivityEvent | StageLifecycleEvent;
|
|
64
75
|
export declare function truncateActivityText(value: unknown, limit?: number): string | undefined;
|
package/dist/agent/activity.js
CHANGED
|
@@ -10,8 +10,43 @@
|
|
|
10
10
|
* ACTIVITY_TEXT_LIMIT applies only to tool/message *previews* — never to
|
|
11
11
|
* those Q&A bodies.
|
|
12
12
|
*/
|
|
13
|
-
export const ACTIVITY_TEXT_LIMIT =
|
|
14
|
-
export
|
|
13
|
+
export const ACTIVITY_TEXT_LIMIT = 2000;
|
|
14
|
+
export const ACTIVITY_TEXT_LIMIT_ENV = "STAGEFLOW_ACTIVITY_TEXT_LIMIT";
|
|
15
|
+
export const ACTIVITY_VERBOSE_ENV = "STAGEFLOW_ACTIVITY_VERBOSE";
|
|
16
|
+
export function readActivityTextLimit(env = process.env, override) {
|
|
17
|
+
if (override !== undefined) {
|
|
18
|
+
if (Number.isFinite(override) && override >= 1)
|
|
19
|
+
return Math.floor(override);
|
|
20
|
+
return ACTIVITY_TEXT_LIMIT;
|
|
21
|
+
}
|
|
22
|
+
const raw = env[ACTIVITY_TEXT_LIMIT_ENV];
|
|
23
|
+
if (raw === undefined || raw.trim() === "") {
|
|
24
|
+
return ACTIVITY_TEXT_LIMIT;
|
|
25
|
+
}
|
|
26
|
+
const n = Number.parseInt(raw, 10);
|
|
27
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
28
|
+
return ACTIVITY_TEXT_LIMIT;
|
|
29
|
+
}
|
|
30
|
+
return n;
|
|
31
|
+
}
|
|
32
|
+
export function readActivityVerbose(env = process.env, override) {
|
|
33
|
+
if (override !== undefined) {
|
|
34
|
+
return override;
|
|
35
|
+
}
|
|
36
|
+
const raw = env[ACTIVITY_VERBOSE_ENV];
|
|
37
|
+
if (raw === undefined || raw.trim() === "") {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const normalized = raw.trim().toLowerCase();
|
|
41
|
+
if (normalized === "0" ||
|
|
42
|
+
normalized === "false" ||
|
|
43
|
+
normalized === "off" ||
|
|
44
|
+
normalized === "no") {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
export function truncateActivityText(value, limit = readActivityTextLimit()) {
|
|
15
50
|
if (value === undefined || value === null)
|
|
16
51
|
return undefined;
|
|
17
52
|
let text;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type StageActivityEvent } from "./activity.js";
|
|
2
|
+
export declare const TOOL_PROGRESS_THROTTLE_MS = 300;
|
|
2
3
|
export type StageActivityObserver = {
|
|
3
4
|
onAssistantTextDelta(delta: string): void;
|
|
5
|
+
onThinkingDelta(delta: string): void;
|
|
6
|
+
onToolPartialResult(accumulatedText: string): void;
|
|
4
7
|
onStreamBoundary(): void;
|
|
5
8
|
onActivity(activity: StageActivityEvent): void;
|
|
6
9
|
dispose(): void;
|
|
@@ -8,6 +11,7 @@ export type StageActivityObserver = {
|
|
|
8
11
|
export type CreateStageActivityObserverOptions = {
|
|
9
12
|
onActivity?: (event: StageActivityEvent) => void;
|
|
10
13
|
writeStderr?: boolean;
|
|
14
|
+
toolProgressThrottleMs?: number;
|
|
11
15
|
};
|
|
12
16
|
/**
|
|
13
17
|
* Observe StageActivityEvent milestones and optional assistant text deltas.
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { truncateActivityText, } from "./activity.js";
|
|
2
|
+
export const TOOL_PROGRESS_THROTTLE_MS = 300;
|
|
3
|
+
function writeIndentedPreview(preview) {
|
|
4
|
+
for (const line of preview.split("\n")) {
|
|
5
|
+
process.stderr.write(` ${line}\n`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function writeActivityStderr(activity, skipAssistantMessage) {
|
|
2
9
|
switch (activity.event) {
|
|
3
10
|
case "agent_start":
|
|
4
11
|
process.stderr.write(" … agent started\n");
|
|
@@ -11,13 +18,33 @@ function writeActivityStderr(activity) {
|
|
|
11
18
|
return;
|
|
12
19
|
case "tool_start":
|
|
13
20
|
process.stderr.write(` → ${activity.toolName}\n`);
|
|
21
|
+
if (activity.argsPreview) {
|
|
22
|
+
writeIndentedPreview(activity.argsPreview);
|
|
23
|
+
}
|
|
14
24
|
return;
|
|
15
25
|
case "tool_end":
|
|
16
26
|
if (activity.isError) {
|
|
17
27
|
process.stderr.write(` ✕ ${activity.toolName}\n`);
|
|
18
28
|
}
|
|
29
|
+
else if (activity.resultPreview) {
|
|
30
|
+
process.stderr.write(` ← ${activity.toolName}\n`);
|
|
31
|
+
}
|
|
32
|
+
if (activity.resultPreview) {
|
|
33
|
+
writeIndentedPreview(activity.resultPreview);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
case "tool_progress":
|
|
19
37
|
return;
|
|
20
38
|
case "message":
|
|
39
|
+
if (activity.role === "assistant" && skipAssistantMessage) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (activity.role === "thinking") {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (activity.text) {
|
|
46
|
+
writeIndentedPreview(activity.text);
|
|
47
|
+
}
|
|
21
48
|
return;
|
|
22
49
|
}
|
|
23
50
|
}
|
|
@@ -28,39 +55,175 @@ function writeActivityStderr(activity) {
|
|
|
28
55
|
*/
|
|
29
56
|
export function createStageActivityObserver(options = {}) {
|
|
30
57
|
const writeStderr = options.writeStderr !== false;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
58
|
+
const toolProgressThrottleMs = options.toolProgressThrottleMs ?? TOOL_PROGRESS_THROTTLE_MS;
|
|
59
|
+
let openStream = "none";
|
|
60
|
+
let assistantStreamedForMessage = false;
|
|
61
|
+
let lastToolPartial = "";
|
|
62
|
+
let thinkingBuffer = "";
|
|
63
|
+
let openTool = null;
|
|
64
|
+
let toolProgressAccum = "";
|
|
65
|
+
let toolProgressPending = false;
|
|
66
|
+
let toolProgressTimer = null;
|
|
67
|
+
const endOpenStream = () => {
|
|
68
|
+
if (openStream === "none") {
|
|
34
69
|
return;
|
|
35
70
|
}
|
|
36
71
|
if (writeStderr) {
|
|
37
72
|
process.stderr.write("\n");
|
|
38
73
|
}
|
|
39
|
-
|
|
74
|
+
openStream = "none";
|
|
75
|
+
};
|
|
76
|
+
const flushThinking = () => {
|
|
77
|
+
const raw = thinkingBuffer;
|
|
78
|
+
thinkingBuffer = "";
|
|
79
|
+
if (!raw.trim()) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
options.onActivity?.({
|
|
83
|
+
event: "message",
|
|
84
|
+
role: "thinking",
|
|
85
|
+
text: truncateActivityText(raw),
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const emitToolProgress = () => {
|
|
89
|
+
if (!toolProgressPending || !openTool) {
|
|
90
|
+
toolProgressPending = false;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const textPreview = truncateActivityText(toolProgressAccum);
|
|
94
|
+
toolProgressPending = false;
|
|
95
|
+
if (!textPreview?.trim()) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
options.onActivity?.({
|
|
99
|
+
event: "tool_progress",
|
|
100
|
+
toolName: openTool.toolName,
|
|
101
|
+
toolCallId: openTool.toolCallId,
|
|
102
|
+
textPreview,
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const cancelToolProgressTimer = () => {
|
|
106
|
+
if (toolProgressTimer !== null) {
|
|
107
|
+
clearTimeout(toolProgressTimer);
|
|
108
|
+
toolProgressTimer = null;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const flushToolProgress = () => {
|
|
112
|
+
cancelToolProgressTimer();
|
|
113
|
+
emitToolProgress();
|
|
114
|
+
};
|
|
115
|
+
const scheduleToolProgress = () => {
|
|
116
|
+
if (!openTool || !toolProgressAccum.trim()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
toolProgressPending = true;
|
|
120
|
+
if (toolProgressTimer !== null) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
toolProgressTimer = setTimeout(() => {
|
|
124
|
+
toolProgressTimer = null;
|
|
125
|
+
emitToolProgress();
|
|
126
|
+
}, toolProgressThrottleMs);
|
|
40
127
|
};
|
|
41
128
|
return {
|
|
42
129
|
onAssistantTextDelta(delta) {
|
|
43
130
|
if (!writeStderr || !delta) {
|
|
44
131
|
return;
|
|
45
132
|
}
|
|
46
|
-
|
|
133
|
+
assistantStreamedForMessage = true;
|
|
134
|
+
if (openStream !== "assistant") {
|
|
135
|
+
endOpenStream();
|
|
47
136
|
process.stderr.write(" ");
|
|
48
|
-
|
|
137
|
+
openStream = "assistant";
|
|
138
|
+
}
|
|
139
|
+
process.stderr.write(delta);
|
|
140
|
+
},
|
|
141
|
+
onThinkingDelta(delta) {
|
|
142
|
+
if (!delta) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
thinkingBuffer += delta;
|
|
146
|
+
if (!writeStderr) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (openStream !== "thinking") {
|
|
150
|
+
endOpenStream();
|
|
151
|
+
process.stderr.write(" ∴ ");
|
|
152
|
+
openStream = "thinking";
|
|
153
|
+
}
|
|
154
|
+
process.stderr.write(delta);
|
|
155
|
+
},
|
|
156
|
+
onToolPartialResult(accumulatedText) {
|
|
157
|
+
if (!accumulatedText) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
toolProgressAccum = accumulatedText;
|
|
161
|
+
scheduleToolProgress();
|
|
162
|
+
if (!writeStderr) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const delta = accumulatedText.startsWith(lastToolPartial)
|
|
166
|
+
? accumulatedText.slice(lastToolPartial.length)
|
|
167
|
+
: accumulatedText;
|
|
168
|
+
lastToolPartial = accumulatedText;
|
|
169
|
+
if (!delta) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (openStream !== "tool") {
|
|
173
|
+
endOpenStream();
|
|
174
|
+
process.stderr.write(" │ ");
|
|
175
|
+
openStream = "tool";
|
|
49
176
|
}
|
|
50
177
|
process.stderr.write(delta);
|
|
51
178
|
},
|
|
52
179
|
onStreamBoundary() {
|
|
53
|
-
|
|
180
|
+
endOpenStream();
|
|
181
|
+
flushThinking();
|
|
182
|
+
flushToolProgress();
|
|
183
|
+
lastToolPartial = "";
|
|
54
184
|
},
|
|
55
185
|
onActivity(activity) {
|
|
56
|
-
|
|
186
|
+
flushThinking();
|
|
187
|
+
if (activity.event === "tool_end") {
|
|
188
|
+
flushToolProgress();
|
|
189
|
+
}
|
|
190
|
+
endOpenStream();
|
|
191
|
+
lastToolPartial = "";
|
|
192
|
+
if (activity.event === "turn_start") {
|
|
193
|
+
assistantStreamedForMessage = false;
|
|
194
|
+
}
|
|
195
|
+
if (activity.event === "tool_start") {
|
|
196
|
+
openTool = {
|
|
197
|
+
toolName: activity.toolName,
|
|
198
|
+
toolCallId: activity.toolCallId,
|
|
199
|
+
};
|
|
200
|
+
toolProgressAccum = "";
|
|
201
|
+
toolProgressPending = false;
|
|
202
|
+
cancelToolProgressTimer();
|
|
203
|
+
}
|
|
204
|
+
else if (activity.event === "tool_end") {
|
|
205
|
+
openTool = null;
|
|
206
|
+
toolProgressAccum = "";
|
|
207
|
+
toolProgressPending = false;
|
|
208
|
+
cancelToolProgressTimer();
|
|
209
|
+
}
|
|
210
|
+
const skipAssistantMessage = activity.event === "message" &&
|
|
211
|
+
activity.role === "assistant" &&
|
|
212
|
+
assistantStreamedForMessage;
|
|
213
|
+
if (activity.event === "message" &&
|
|
214
|
+
activity.role === "assistant") {
|
|
215
|
+
assistantStreamedForMessage = false;
|
|
216
|
+
}
|
|
57
217
|
options.onActivity?.(activity);
|
|
58
218
|
if (writeStderr) {
|
|
59
|
-
writeActivityStderr(activity);
|
|
219
|
+
writeActivityStderr(activity, skipAssistantMessage);
|
|
60
220
|
}
|
|
61
221
|
},
|
|
62
222
|
dispose() {
|
|
63
|
-
|
|
223
|
+
endOpenStream();
|
|
224
|
+
flushThinking();
|
|
225
|
+
flushToolProgress();
|
|
226
|
+
cancelToolProgressTimer();
|
|
64
227
|
},
|
|
65
228
|
};
|
|
66
229
|
}
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { type StageProviderSupport } from "./providerSupport.js";
|
|
2
|
-
|
|
3
|
-
* Locate the pi-cursor-sdk extension entry without opening full global package
|
|
4
|
-
* discovery. Stages stay sealed; only this allowlisted path is loaded.
|
|
5
|
-
*
|
|
6
|
-
* Resolution order:
|
|
7
|
-
* 1. STAGEFLOW_CURSOR_EXTENSION (absolute path to the extension .ts/.js)
|
|
8
|
-
* 2. Path package from ~/.pi/agent/settings.json (same source interactive pi uses)
|
|
9
|
-
* 3. npm install under ~/.pi/agent/npm/node_modules/pi-cursor-sdk
|
|
10
|
-
* 4. Sibling checkout at ../pi-cursor-sdk relative to this repo
|
|
11
|
-
*/
|
|
2
|
+
export declare function cursorExtensionEntryInPackage(packageRoot: string): string | undefined;
|
|
12
3
|
export declare function resolveCursorExtensionPath(): string | undefined;
|
|
13
4
|
export declare function isCursorModelRef(modelRef: string): boolean;
|
|
14
5
|
export declare const cursorProviderSupport: StageProviderSupport;
|
|
@@ -18,8 +18,19 @@ const CURSOR_SETTING_SOURCES_ENV = "PI_CURSOR_SETTING_SOURCES";
|
|
|
18
18
|
* 1. STAGEFLOW_CURSOR_EXTENSION (absolute path to the extension .ts/.js)
|
|
19
19
|
* 2. Path package from ~/.pi/agent/settings.json (same source interactive pi uses)
|
|
20
20
|
* 3. npm install under ~/.pi/agent/npm/node_modules/pi-cursor-sdk
|
|
21
|
+
* (`dist/index.js` for 0.3+, `src/index.ts` for older publishes)
|
|
21
22
|
* 4. Sibling checkout at ../pi-cursor-sdk relative to this repo
|
|
22
23
|
*/
|
|
24
|
+
const CURSOR_PACKAGE_ENTRIES = ["dist/index.js", "src/index.ts"];
|
|
25
|
+
export function cursorExtensionEntryInPackage(packageRoot) {
|
|
26
|
+
for (const rel of CURSOR_PACKAGE_ENTRIES) {
|
|
27
|
+
const full = path.join(packageRoot, rel);
|
|
28
|
+
if (existsSync(full)) {
|
|
29
|
+
return full;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
23
34
|
export function resolveCursorExtensionPath() {
|
|
24
35
|
const fromEnv = process.env.STAGEFLOW_CURSOR_EXTENSION?.trim();
|
|
25
36
|
if (fromEnv && existsSync(fromEnv)) {
|
|
@@ -29,16 +40,13 @@ export function resolveCursorExtensionPath() {
|
|
|
29
40
|
if (fromSettings) {
|
|
30
41
|
return fromSettings;
|
|
31
42
|
}
|
|
32
|
-
const npmEntry = path.join(os.homedir(), ".pi", "agent", "npm", "node_modules", "pi-cursor-sdk"
|
|
33
|
-
if (
|
|
43
|
+
const npmEntry = cursorExtensionEntryInPackage(path.join(os.homedir(), ".pi", "agent", "npm", "node_modules", "pi-cursor-sdk"));
|
|
44
|
+
if (npmEntry) {
|
|
34
45
|
return npmEntry;
|
|
35
46
|
}
|
|
36
47
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
return sibling;
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
48
|
+
const siblingRoot = path.resolve(here, "../../../pi-cursor-sdk");
|
|
49
|
+
return cursorExtensionEntryInPackage(siblingRoot);
|
|
42
50
|
}
|
|
43
51
|
export function isCursorModelRef(modelRef) {
|
|
44
52
|
const slash = modelRef.indexOf("/");
|
|
@@ -60,7 +68,7 @@ function missingExtensionReason(modelRef) {
|
|
|
60
68
|
return [
|
|
61
69
|
`Model "${modelRef}" requires pi-cursor-sdk, but no extension entry was found.`,
|
|
62
70
|
"Install with `pi install npm:pi-cursor-sdk`, or set STAGEFLOW_CURSOR_EXTENSION",
|
|
63
|
-
"to the absolute path of pi-cursor-sdk/src/index.ts.",
|
|
71
|
+
"to the absolute path of pi-cursor-sdk/dist/index.js (or src/index.ts).",
|
|
64
72
|
"Also ensure a Cursor SDK API key is available via Pi /login or CURSOR_API_KEY.",
|
|
65
73
|
].join(" ");
|
|
66
74
|
}
|
|
@@ -130,8 +138,8 @@ function resolveFromPiSettings() {
|
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
}
|
|
133
|
-
const declared =
|
|
134
|
-
if (
|
|
141
|
+
const declared = cursorExtensionEntryInPackage(packageRoot);
|
|
142
|
+
if (declared) {
|
|
135
143
|
return declared;
|
|
136
144
|
}
|
|
137
145
|
}
|