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.
Files changed (224) hide show
  1. package/README.md +179 -14
  2. package/dist/agent/activity.d.ts +12 -1
  3. package/dist/agent/activity.js +37 -2
  4. package/dist/agent/activityObserver.d.ts +5 -1
  5. package/dist/agent/activityObserver.js +174 -11
  6. package/dist/agent/cursorProvider.d.ts +1 -10
  7. package/dist/agent/cursorProvider.js +18 -10
  8. package/dist/agent/fakeAgent.js +15 -7
  9. package/dist/agent/piAdapter.d.ts +18 -1
  10. package/dist/agent/piAdapter.js +115 -45
  11. package/dist/agent/port.d.ts +7 -0
  12. package/dist/agent/port.js +3 -0
  13. package/dist/agent/providerAuth.js +12 -7
  14. package/dist/catalog/displayCatalogPath.d.ts +12 -0
  15. package/dist/catalog/displayCatalogPath.js +28 -0
  16. package/dist/cli/artifactCommand.d.ts +10 -0
  17. package/dist/cli/artifactCommand.js +120 -0
  18. package/dist/cli/envelopeCommand.d.ts +32 -0
  19. package/dist/cli/envelopeCommand.js +285 -0
  20. package/dist/cli/exportRunCommand.d.ts +10 -0
  21. package/dist/cli/exportRunCommand.js +150 -0
  22. package/dist/cli/handoffFormat.d.ts +22 -0
  23. package/dist/cli/handoffFormat.js +61 -0
  24. package/dist/cli/initCommand.d.ts +9 -0
  25. package/dist/cli/initCommand.js +71 -0
  26. package/dist/cli/initTemplates.d.ts +3 -0
  27. package/dist/cli/initTemplates.js +19 -0
  28. package/dist/cli/operatorCatalog.d.ts +10 -0
  29. package/dist/cli/operatorCatalog.js +16 -0
  30. package/dist/cli/runCommand.d.ts +7 -1
  31. package/dist/cli/runCommand.js +83 -11
  32. package/dist/cli/runOutput.d.ts +4 -1
  33. package/dist/cli/runOutput.js +36 -8
  34. package/dist/cli/skillsCommand.d.ts +16 -0
  35. package/dist/cli/skillsCommand.js +459 -0
  36. package/dist/cli/validateCommand.d.ts +1 -1
  37. package/dist/cli/validateCommand.js +20 -3
  38. package/dist/cli.d.ts +1 -0
  39. package/dist/cli.js +128 -22
  40. package/dist/config/browseCatalog.d.ts +45 -0
  41. package/dist/config/browseCatalog.js +170 -0
  42. package/dist/config/createPipeline.d.ts +12 -3
  43. package/dist/config/createPipeline.js +292 -100
  44. package/dist/config/createStage.d.ts +10 -4
  45. package/dist/config/createStage.js +44 -14
  46. package/dist/config/listConfig.d.ts +14 -19
  47. package/dist/config/listConfig.js +26 -191
  48. package/dist/config/listModelsFromManifest.d.ts +2 -0
  49. package/dist/config/listModelsFromManifest.js +4 -0
  50. package/dist/config/loadOutcome.d.ts +4 -0
  51. package/dist/config/loadPipeline.d.ts +1 -15
  52. package/dist/config/loadPipeline.js +88 -142
  53. package/dist/config/loadStage.d.ts +4 -0
  54. package/dist/config/loadStage.js +60 -32
  55. package/dist/config/loadStageflowManifest.d.ts +5 -0
  56. package/dist/config/loadStageflowManifest.js +157 -0
  57. package/dist/config/mergePipelineIncludes.d.ts +9 -0
  58. package/dist/config/mergePipelineIncludes.js +141 -0
  59. package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
  60. package/dist/config/normalizePipelineStageEntry.js +210 -0
  61. package/dist/config/pipelineStageKeys.d.ts +4 -0
  62. package/dist/config/pipelineStageKeys.js +14 -0
  63. package/dist/config/resolveCatalogContext.d.ts +13 -0
  64. package/dist/config/resolveCatalogContext.js +13 -0
  65. package/dist/config/resolveForkEmitContext.d.ts +4 -0
  66. package/dist/config/resolveForkEmitContext.js +34 -0
  67. package/dist/config/resolvePipelineDag.d.ts +5 -1
  68. package/dist/config/resolvePipelineDag.js +86 -36
  69. package/dist/config/scanCatalogPaths.d.ts +4 -0
  70. package/dist/config/scanCatalogPaths.js +83 -0
  71. package/dist/config/validateCatalog.d.ts +22 -4
  72. package/dist/config/validateCatalog.js +169 -132
  73. package/dist/envelope/check.d.ts +2 -0
  74. package/dist/envelope/check.js +58 -0
  75. package/dist/envelope/cloneForks.d.ts +2 -0
  76. package/dist/envelope/cloneForks.js +62 -0
  77. package/dist/envelope/forkChoice.d.ts +3 -0
  78. package/dist/envelope/forkChoice.js +47 -0
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.js +6 -0
  81. package/dist/mcp/catalogTools.d.ts +3 -0
  82. package/dist/mcp/catalogTools.js +187 -0
  83. package/dist/mcp/controlTools.d.ts +3 -0
  84. package/dist/mcp/controlTools.js +147 -0
  85. package/dist/mcp/deps.d.ts +11 -0
  86. package/dist/mcp/deps.js +1 -0
  87. package/dist/mcp/projectRun.d.ts +1 -37
  88. package/dist/mcp/projectRun.js +1 -57
  89. package/dist/mcp/resources.d.ts +5 -0
  90. package/dist/mcp/resources.js +45 -0
  91. package/dist/mcp/server.d.ts +16 -3
  92. package/dist/mcp/server.js +159 -3
  93. package/dist/mcp/toolResults.d.ts +7 -0
  94. package/dist/mcp/toolResults.js +6 -0
  95. package/dist/mcp/tools.d.ts +2 -7
  96. package/dist/mcp/tools.js +52 -86
  97. package/dist/mcp/waitRun.d.ts +46 -0
  98. package/dist/mcp/waitRun.js +146 -0
  99. package/dist/mcp/waitingGates.d.ts +12 -0
  100. package/dist/mcp/waitingGates.js +83 -0
  101. package/dist/project/findProjectRoot.d.ts +2 -0
  102. package/dist/project/findProjectRoot.js +51 -0
  103. package/dist/project/globalHome.d.ts +2 -0
  104. package/dist/project/globalHome.js +19 -0
  105. package/dist/project/resolveProjectContext.d.ts +9 -0
  106. package/dist/project/resolveProjectContext.js +38 -0
  107. package/dist/project/resolveStageflowContext.d.ts +11 -0
  108. package/dist/project/resolveStageflowContext.js +66 -0
  109. package/dist/projection/projectRun.d.ts +44 -0
  110. package/dist/projection/projectRun.js +71 -0
  111. package/dist/prompt/priorEnvelope.d.ts +1 -1
  112. package/dist/prompt/priorEnvelope.js +4 -1
  113. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  114. package/dist/runstore/normalizeCatalogPath.js +4 -0
  115. package/dist/runstore/paths.js +2 -0
  116. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  117. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  118. package/dist/runstore/port.d.ts +22 -2
  119. package/dist/runstore/port.js +4 -1
  120. package/dist/runstore/runProjection.d.ts +1 -1
  121. package/dist/runstore/runProjection.js +25 -4
  122. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  123. package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
  124. package/dist/runstore/sqlite/schema.d.ts +1 -1
  125. package/dist/runstore/sqlite/schema.js +4 -1
  126. package/dist/runstore/stageInstanceId.d.ts +4 -0
  127. package/dist/runstore/stageInstanceId.js +23 -0
  128. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  129. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  130. package/dist/runstore/trackProjection.js +28 -12
  131. package/dist/runstore/workspaceLayout.d.ts +1 -0
  132. package/dist/runstore/workspaceLayout.js +1 -1
  133. package/dist/runtime/cloneSchedule.d.ts +18 -0
  134. package/dist/runtime/cloneSchedule.js +233 -0
  135. package/dist/runtime/credentialBinding.d.ts +8 -6
  136. package/dist/runtime/credentialBinding.js +18 -24
  137. package/dist/runtime/envelopeRouting.d.ts +2 -1
  138. package/dist/runtime/envelopeRouting.js +80 -13
  139. package/dist/runtime/pipelineRunner.d.ts +3 -0
  140. package/dist/runtime/pipelineRunner.js +15 -1
  141. package/dist/runtime/pipelineScheduler.d.ts +8 -2
  142. package/dist/runtime/pipelineScheduler.js +215 -39
  143. package/dist/runtime/reloadRunCatalog.d.ts +5 -0
  144. package/dist/runtime/reloadRunCatalog.js +44 -0
  145. package/dist/runtime/resumeReconstruct.d.ts +1 -0
  146. package/dist/runtime/resumeReconstruct.js +31 -10
  147. package/dist/runtime/runChangeBus.d.ts +18 -0
  148. package/dist/runtime/runChangeBus.js +83 -0
  149. package/dist/runtime/runManager.d.ts +4 -0
  150. package/dist/runtime/runManager.js +36 -16
  151. package/dist/runtime/settingsFile.d.ts +12 -0
  152. package/dist/runtime/settingsFile.js +87 -6
  153. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  154. package/dist/runtime/stageAttemptBootstrap.js +18 -7
  155. package/dist/runtime/stageProcessLauncher.js +3 -0
  156. package/dist/runtime/stageRecovery.js +3 -1
  157. package/dist/runtime/stageRunner.d.ts +1 -0
  158. package/dist/runtime/stageRunner.js +17 -15
  159. package/dist/runtime/stageWorker.js +13 -10
  160. package/dist/server/bootstrap.d.ts +32 -0
  161. package/dist/server/bootstrap.js +56 -0
  162. package/dist/server/createHttpHost.d.ts +34 -0
  163. package/dist/server/createHttpHost.js +70 -0
  164. package/dist/server/http.d.ts +6 -13
  165. package/dist/server/http.js +297 -352
  166. package/dist/server/mcpHost.d.ts +22 -0
  167. package/dist/server/mcpHost.js +21 -0
  168. package/dist/server/operatorResults.d.ts +13 -0
  169. package/dist/server/operatorResults.js +22 -0
  170. package/dist/tools/emitStageEnvelope.d.ts +12 -1
  171. package/dist/tools/emitStageEnvelope.js +27 -1
  172. package/dist/types/envelope.d.ts +3 -0
  173. package/dist/types/forkChoice.d.ts +30 -0
  174. package/dist/types/forkChoice.js +1 -0
  175. package/dist/types/pipeline.d.ts +59 -2
  176. package/dist/types/stageflowManifest.d.ts +23 -0
  177. package/dist/types/stageflowManifest.js +1 -0
  178. package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
  179. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  180. package/dist/ui/index.html +3 -2
  181. package/dist/ui/stageflow-icon.svg +12 -0
  182. package/package.json +8 -5
  183. package/skills/install-suite.sh +107 -0
  184. package/skills/stageflow/SKILL.md +26 -0
  185. package/skills/stageflow/references/control-surface.md +26 -0
  186. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  187. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  188. package/skills/stageflow-author/SKILL.md +58 -0
  189. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  190. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  191. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  192. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  193. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  194. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  195. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  196. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  197. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  198. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  199. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  200. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  201. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  202. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  203. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  204. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  205. package/skills/stageflow-delegate/SKILL.md +18 -0
  206. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  207. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  208. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  209. package/skills/stageflow-run/SKILL.md +151 -0
  210. package/skills/stageflow-run/references/mcp-call.md +17 -0
  211. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  212. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  213. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  214. package/skills/stageflow-session-capture/SKILL.md +94 -0
  215. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  216. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  217. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  218. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  219. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  220. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  221. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  222. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  223. package/skills/stageflow-setup/SKILL.md +110 -0
  224. package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
@@ -2,9 +2,11 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "
2
2
  import path from "node:path";
3
3
  import { assertRequiredEnvelope, isAdvancingEnvelope, } from "../envelope/check.js";
4
4
  import { assertEnvelopePayload } from "../envelope/payloadSchema.js";
5
+ import { assertCloneForks } from "../envelope/cloneForks.js";
6
+ import { assertForkEnvelope } from "../envelope/forkChoice.js";
5
7
  import { assertAnswerMatchesPrompt, parseAskOperatorAnswer, } from "../tools/askOperator.js";
6
8
  import { tryParsePendingPrompt } from "../runtime/stageHitl.js";
7
- import { runStageViaOpen } from "./port.js";
9
+ import { runtimeStageId, runStageViaOpen } from "./port.js";
8
10
  function opaqueEqual(a, b) {
9
11
  if (Object.is(a, b))
10
12
  return true;
@@ -82,7 +84,7 @@ export class FakeAgent {
82
84
  return answer;
83
85
  };
84
86
  if (behavior.type === "wait_then_emit") {
85
- const loaded = loadFakeHitlResume(input.roots, input.stage.id);
87
+ const loaded = loadFakeHitlResume(input.roots, runtimeStageId(input));
86
88
  if (loaded === "corrupt") {
87
89
  resumeCorrupt = true;
88
90
  }
@@ -115,7 +117,7 @@ export class FakeAgent {
115
117
  });
116
118
  };
117
119
  const finishEmit = () => {
118
- clearFakeHitlResume(input.roots, input.stage.id);
120
+ clearFakeHitlResume(input.roots, runtimeStageId(input));
119
121
  if (behavior.type === "throw") {
120
122
  input.onActivity?.({ event: "agent_end" });
121
123
  return {
@@ -135,6 +137,12 @@ export class FakeAgent {
135
137
  : undefined;
136
138
  try {
137
139
  const envelope = assertRequiredEnvelope(envelopeValue);
140
+ if (input.forkEmitContext !== undefined) {
141
+ assertForkEnvelope(envelope, input.forkEmitContext);
142
+ }
143
+ if (input.cloneEmitContext !== undefined) {
144
+ assertCloneForks(envelope, input.cloneEmitContext);
145
+ }
138
146
  assertEnvelopePayload(envelope, input.stage.payload_schema);
139
147
  if (!isAdvancingEnvelope(envelope)) {
140
148
  input.onActivity?.({ event: "agent_end" });
@@ -167,7 +175,7 @@ export class FakeAgent {
167
175
  }
168
176
  };
169
177
  return {
170
- stageId: input.stage.id,
178
+ stageId: runtimeStageId(input),
171
179
  async next() {
172
180
  if (closed) {
173
181
  return {
@@ -206,7 +214,7 @@ export class FakeAgent {
206
214
  }
207
215
  catch (err) {
208
216
  input.onActivity?.({ event: "agent_end" });
209
- clearFakeHitlResume(input.roots, input.stage.id);
217
+ clearFakeHitlResume(input.roots, runtimeStageId(input));
210
218
  return {
211
219
  status: "completed",
212
220
  result: {
@@ -223,7 +231,7 @@ export class FakeAgent {
223
231
  answer !== undefined &&
224
232
  !opaqueEqual(answer, expected)) {
225
233
  input.onActivity?.({ event: "agent_end" });
226
- clearFakeHitlResume(input.roots, input.stage.id);
234
+ clearFakeHitlResume(input.roots, runtimeStageId(input));
227
235
  return {
228
236
  status: "completed",
229
237
  result: {
@@ -237,7 +245,7 @@ export class FakeAgent {
237
245
  const request = behavior.waitRequests[waitIndex];
238
246
  waitIndex += 1;
239
247
  armWait();
240
- writeFakeHitlResume(input.roots, input.stage.id, {
248
+ writeFakeHitlResume(input.roots, runtimeStageId(input), {
241
249
  waitRequests: behavior.waitRequests,
242
250
  expectedAnswers: behavior.expectedAnswers,
243
251
  envelope: behavior.envelope,
@@ -2,6 +2,7 @@ import { type AgentSession, DefaultResourceLoader, SessionManager, SettingsManag
2
2
  import type { StageRoots } from "../runtime/stageRoots.js";
3
3
  import { type AskOperatorPrompt, type AskOperatorWaitBridge } from "../tools/askOperator.js";
4
4
  import "./cursorProvider.js";
5
+ import { type StageActivityObserver } from "./activityObserver.js";
5
6
  import type { AgentPort, OpaqueAnswer, StageHandle, StageHandleCloseOptions, StageRunInput, StageRunResult } from "./port.js";
6
7
  /**
7
8
  * Stage tool allowlist for sealed Pi sessions.
@@ -45,6 +46,21 @@ export type ConnectedAskWaitStageHandleOptions = {
45
46
  * runtime never imports the channel.
46
47
  */
47
48
  export declare function createConnectedAskWaitStageHandle(options: ConnectedAskWaitStageHandleOptions): StageHandle;
49
+ /**
50
+ * Extract display text from a Pi tool_execution_update partialResult.
51
+ * Prefers content[].text blocks; otherwise JSON-stringifies.
52
+ */
53
+ export declare function extractPartialResultText(partialResult: unknown): string;
54
+ export type RouteSessionEventToProgressOptions = {
55
+ observer: StageActivityObserver;
56
+ verbose: boolean;
57
+ };
58
+ /**
59
+ * Adapter-edge routing: stream deltas to the observer, map milestones to
60
+ * StageActivityEvent. Verbose thinking / live tool partials go to observer
61
+ * hooks (stderr streams + coalesced/throttled persist via onActivity).
62
+ */
63
+ export declare function routeSessionEventToProgress(event: Record<string, unknown>, options: RouteSessionEventToProgressOptions): void;
48
64
  export declare function composeStageUserPrompt(input: StageRunInput, emitToolName: string, emitHintOverride?: string, artifactToolName?: string): string;
49
65
  /**
50
66
  * DefaultResourceLoader with host/global discovery turned off.
@@ -68,6 +84,7 @@ export declare function createSealedResourceLoader(options: {
68
84
  }): DefaultResourceLoader;
69
85
  /** Stable Pi session JSONL path under the run workspace stage dir. */
70
86
  export declare function stageSessionFilePath(roots: Pick<StageRoots, "runWorkspaceDir" | "attempt">, stageId: string): string;
87
+ export declare function resolveStageSessionFile(input: StageRunInput): string;
71
88
  export declare class StageSessionReconstructError extends Error {
72
89
  readonly stageId: string;
73
90
  readonly sessionFile: string;
@@ -81,7 +98,7 @@ export declare class StageSessionReconstructError extends Error {
81
98
  * Create or reopen the durable stage session file under the run workspace.
82
99
  * Touches an empty file first so Pi flushes the session header immediately.
83
100
  */
84
- export declare function createStageSessionManager(roots: StageRoots, stageId: string): Promise<SessionManager>;
101
+ export declare function createStageSessionManager(roots: StageRoots, stageId: string, sessionFile?: string): Promise<SessionManager>;
85
102
  /**
86
103
  * Open an existing stage session for post-restart reconstruct.
87
104
  * Missing or corrupt files fail closed (KTD7) — never create a fresh session.
@@ -41,9 +41,9 @@ import { createAskOperatorTool, } from "../tools/askOperator.js";
41
41
  import { createWriteStageArtifactTool } from "../tools/writeStageArtifact.js";
42
42
  import "./cursorProvider.js";
43
43
  import { findProviderSupport } from "./providerSupport.js";
44
- import { mapSessionEventToActivity } from "./activity.js";
45
- import { createStageActivityObserver } from "./activityObserver.js";
46
- import { DEFAULT_STAGE_TIMEOUT_MS, runStageViaOpen } from "./port.js";
44
+ import { mapSessionEventToActivity, readActivityVerbose } from "./activity.js";
45
+ import { createStageActivityObserver, } from "./activityObserver.js";
46
+ import { DEFAULT_STAGE_TIMEOUT_MS, runtimeStageId, runStageViaOpen } from "./port.js";
47
47
  /**
48
48
  * Stage tool allowlist for sealed Pi sessions.
49
49
  * Always includes `ask_operator` (R7); `write_stage_artifact` when registered
@@ -260,31 +260,98 @@ export function createConnectedAskWaitStageHandle(options) {
260
260
  },
261
261
  };
262
262
  }
263
+ /**
264
+ * Extract display text from a Pi tool_execution_update partialResult.
265
+ * Prefers content[].text blocks; otherwise JSON-stringifies.
266
+ */
267
+ export function extractPartialResultText(partialResult) {
268
+ if (partialResult === undefined || partialResult === null) {
269
+ return "";
270
+ }
271
+ if (typeof partialResult === "string") {
272
+ return partialResult;
273
+ }
274
+ if (typeof partialResult === "object") {
275
+ const content = partialResult.content;
276
+ if (Array.isArray(content)) {
277
+ const parts = [];
278
+ for (const block of content) {
279
+ if (!block || typeof block !== "object")
280
+ continue;
281
+ const b = block;
282
+ if (b.type === "text" && typeof b.text === "string") {
283
+ parts.push(b.text);
284
+ }
285
+ }
286
+ if (parts.length > 0) {
287
+ return parts.join("");
288
+ }
289
+ }
290
+ try {
291
+ return JSON.stringify(partialResult);
292
+ }
293
+ catch {
294
+ return String(partialResult);
295
+ }
296
+ }
297
+ return String(partialResult);
298
+ }
299
+ /**
300
+ * Adapter-edge routing: stream deltas to the observer, map milestones to
301
+ * StageActivityEvent. Verbose thinking / live tool partials go to observer
302
+ * hooks (stderr streams + coalesced/throttled persist via onActivity).
303
+ */
304
+ export function routeSessionEventToProgress(event, options) {
305
+ const { observer, verbose } = options;
306
+ if (event.type === "message_update") {
307
+ const ame = event.assistantMessageEvent;
308
+ if (ame && typeof ame === "object") {
309
+ const update = ame;
310
+ if (update.type === "text_delta") {
311
+ observer.onAssistantTextDelta(update.delta ?? "");
312
+ return;
313
+ }
314
+ if (verbose && update.type === "thinking_delta") {
315
+ observer.onThinkingDelta(update.delta ?? "");
316
+ return;
317
+ }
318
+ }
319
+ return;
320
+ }
321
+ if (verbose &&
322
+ event.type === "tool_execution_update") {
323
+ const text = extractPartialResultText(event.partialResult);
324
+ if (text) {
325
+ observer.onToolPartialResult(text);
326
+ }
327
+ return;
328
+ }
329
+ if (event.type === "message_end" || event.type === "agent_end") {
330
+ observer.onStreamBoundary();
331
+ }
332
+ if (event.type === "agent_start" ||
333
+ event.type === "turn_start" ||
334
+ event.type === "tool_execution_start") {
335
+ observer.onStreamBoundary();
336
+ }
337
+ const activity = mapSessionEventToActivity(event);
338
+ if (!activity) {
339
+ return;
340
+ }
341
+ observer.onActivity(activity);
342
+ }
263
343
  /**
264
344
  * Map Pi session events to StageActivityEvent at the adapter edge, then
265
345
  * forward to the activity observer (stderr + onActivity).
266
346
  */
267
347
  function attachStageProgress(session, onActivity) {
268
348
  const observer = createStageActivityObserver({ onActivity, writeStderr: true });
349
+ const verbose = readActivityVerbose();
269
350
  const unsubscribe = session.subscribe((event) => {
270
- if (event.type === "message_update" &&
271
- event.assistantMessageEvent.type === "text_delta") {
272
- observer.onAssistantTextDelta(event.assistantMessageEvent.delta ?? "");
273
- return;
274
- }
275
- if (event.type === "message_end" || event.type === "agent_end") {
276
- observer.onStreamBoundary();
277
- }
278
- if (event.type === "agent_start" ||
279
- event.type === "turn_start" ||
280
- event.type === "tool_execution_start") {
281
- observer.onStreamBoundary();
282
- }
283
- const activity = mapSessionEventToActivity(event);
284
- if (!activity) {
285
- return;
286
- }
287
- observer.onActivity(activity);
351
+ routeSessionEventToProgress(event, {
352
+ observer,
353
+ verbose,
354
+ });
288
355
  });
289
356
  return () => {
290
357
  unsubscribe();
@@ -298,7 +365,7 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
298
365
  emitHint += `\nOn status=success, payload is required and must match this JSON Schema:\n${JSON.stringify(input.stage.payload_schema, null, 2)}`;
299
366
  }
300
367
  const attempt = input.roots.attempt ?? 1;
301
- const attemptArtifactsPath = `stages/${input.stage.id}/attempts/${attempt}/artifacts/`;
368
+ const attemptArtifactsPath = `stages/${runtimeStageId(input)}/attempts/${attempt}/artifacts/`;
302
369
  const skillBaseDir = input.stage.skill !== undefined && input.skillFilePath !== undefined
303
370
  ? path.dirname(input.skillFilePath)
304
371
  : undefined;
@@ -323,11 +390,11 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
323
390
  : `Create factory stage artifacts under ${attemptArtifactsPath} relative to the run folder.`;
324
391
  return [
325
392
  `Task id: ${input.task.id}`,
326
- `Stage id: ${input.stage.id}`,
393
+ `Stage id: ${runtimeStageId(input)}`,
327
394
  `Goal: ${input.task.goal}`,
328
395
  input.task.context ? `Context: ${input.task.context}` : "",
329
396
  input.task.constraints ? `Constraints: ${input.task.constraints}` : "",
330
- formatPriorEnvelope(input.priorEnvelope),
397
+ formatPriorEnvelope(input.priorEnvelope, input.priorEnvelopes),
331
398
  "",
332
399
  artifactGuidance,
333
400
  emitHint,
@@ -423,6 +490,10 @@ export function stageSessionFilePath(roots, stageId) {
423
490
  : noAttemptContext();
424
491
  return ctx.sessionPath(roots.runWorkspaceDir, stageId);
425
492
  }
493
+ export function resolveStageSessionFile(input) {
494
+ return (input.resumeToken ??
495
+ stageSessionFilePath(input.roots, runtimeStageId(input)));
496
+ }
426
497
  export class StageSessionReconstructError extends Error {
427
498
  stageId;
428
499
  sessionFile;
@@ -437,8 +508,7 @@ export class StageSessionReconstructError extends Error {
437
508
  * Create or reopen the durable stage session file under the run workspace.
438
509
  * Touches an empty file first so Pi flushes the session header immediately.
439
510
  */
440
- export async function createStageSessionManager(roots, stageId) {
441
- const sessionFile = stageSessionFilePath(roots, stageId);
511
+ export async function createStageSessionManager(roots, stageId, sessionFile = stageSessionFilePath(roots, stageId)) {
442
512
  await mkdir(path.dirname(sessionFile), { recursive: true });
443
513
  if (!(await fileExists(sessionFile))) {
444
514
  await writeFile(sessionFile, "");
@@ -616,7 +686,7 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
616
686
  const provider = findProviderSupport(input.stage.model);
617
687
  const capture = {};
618
688
  const askWaitChannel = existingAskWaitChannel ?? new AskOperatorWaitChannel();
619
- const emitDef = createEmitStageEnvelopeTool(capture, input.stage.payload_schema);
689
+ const emitDef = createEmitStageEnvelopeTool(capture, input.stage.payload_schema, input.forkEmitContext, input.cloneEmitContext);
620
690
  const emitTool = defineTool(emitDef);
621
691
  const askDef = createAskOperatorTool({
622
692
  requestWait: (prompt) => askWaitChannel.requestWait(prompt),
@@ -624,7 +694,7 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
624
694
  const askTool = defineTool(askDef);
625
695
  const artifactDef = createWriteStageArtifactTool({
626
696
  runWorkspaceDir: roots.runWorkspaceDir,
627
- stageId: input.stage.id,
697
+ stageId: runtimeStageId(input),
628
698
  attempt: roots.attempt ?? 1,
629
699
  });
630
700
  const artifactTool = defineTool(artifactDef);
@@ -721,18 +791,18 @@ function isStageRunResult(value) {
721
791
  * Does not auto-continue the model turn — U4 owns the yield loop.
722
792
  */
723
793
  export async function reconstructStageSessionForAnswer(input, answer) {
724
- const sessionManager = await openStageSessionManager(input.roots, input.stage.id);
725
- const sessionFile = ensureStageSessionFlushed(sessionManager, input.stage.id);
794
+ const sessionManager = await openStageSessionManager(input.roots, runtimeStageId(input));
795
+ const sessionFile = ensureStageSessionFlushed(sessionManager, runtimeStageId(input));
726
796
  let injection;
727
797
  try {
728
798
  injection = injectOpaqueAnswerIntoSession(sessionManager, answer);
729
799
  }
730
800
  catch (err) {
731
- throw new StageSessionReconstructError(`failed to inject answer into stage session: ${sessionFile}`, { stageId: input.stage.id, sessionFile, cause: err });
801
+ throw new StageSessionReconstructError(`failed to inject answer into stage session: ${sessionFile}`, { stageId: runtimeStageId(input), sessionFile, cause: err });
732
802
  }
733
803
  const wiring = await prepareStageSessionWiring(input, sessionManager);
734
804
  if (isStageRunResult(wiring)) {
735
- throw new StageSessionReconstructError(wiring.ok === false ? wiring.reason : "stage session reconstruct failed", { stageId: input.stage.id, sessionFile });
805
+ throw new StageSessionReconstructError(wiring.ok === false ? wiring.reason : "stage session reconstruct failed", { stageId: runtimeStageId(input), sessionFile });
736
806
  }
737
807
  let session;
738
808
  try {
@@ -753,7 +823,7 @@ export async function reconstructStageSessionForAnswer(input, answer) {
753
823
  modelRuntime: wiring.modelRuntime,
754
824
  });
755
825
  if (resolved.error || !resolved.model) {
756
- throw new StageSessionReconstructError(resolved.error ?? `Model not found: ${input.stage.model}`, { stageId: input.stage.id, sessionFile });
826
+ throw new StageSessionReconstructError(resolved.error ?? `Model not found: ${input.stage.model}`, { stageId: runtimeStageId(input), sessionFile });
757
827
  }
758
828
  await session.setModel(resolved.model);
759
829
  if (resolved.thinkingLevel) {
@@ -779,7 +849,7 @@ export async function reconstructStageSessionForAnswer(input, answer) {
779
849
  wiring.restoreProvider?.();
780
850
  if (err instanceof StageSessionReconstructError)
781
851
  throw err;
782
- throw new StageSessionReconstructError(`failed to reconstruct stage session: ${err instanceof Error ? err.message : String(err)}`, { stageId: input.stage.id, sessionFile, cause: err });
852
+ throw new StageSessionReconstructError(`failed to reconstruct stage session: ${err instanceof Error ? err.message : String(err)}`, { stageId: runtimeStageId(input), sessionFile, cause: err });
783
853
  }
784
854
  }
785
855
  async function bindStageSession(input, sessionManager, wiring) {
@@ -795,7 +865,7 @@ async function bindStageSession(input, sessionManager, wiring) {
795
865
  settingsManager: wiring.settingsManager,
796
866
  });
797
867
  const session = created.session;
798
- ensureStageSessionFlushed(sessionManager, input.stage.id);
868
+ ensureStageSessionFlushed(sessionManager, runtimeStageId(input));
799
869
  await session.bindExtensions({});
800
870
  const resolved = resolveCliModel({
801
871
  cliModel: input.stage.model,
@@ -830,7 +900,7 @@ export class PiAgentAdapter {
830
900
  openStage(input) {
831
901
  const timeoutMs = input.timeoutMs ?? DEFAULT_STAGE_TIMEOUT_MS;
832
902
  const askWaitChannel = new AskOperatorWaitChannel();
833
- const sessionFile = input.resumeToken ?? stageSessionFilePath(input.roots, input.stage.id);
903
+ const sessionFile = resolveStageSessionFile(input);
834
904
  let sessionManager;
835
905
  let resumeWaiting = false;
836
906
  if (existsSync(sessionFile)) {
@@ -839,14 +909,14 @@ export class PiAgentAdapter {
839
909
  }
840
910
  catch (err) {
841
911
  throw new StageSessionReconstructError(`stage session file corrupt or unreadable for resume: ${sessionFile}`, {
842
- stageId: input.stage.id,
912
+ stageId: runtimeStageId(input),
843
913
  sessionFile,
844
914
  cause: err,
845
915
  });
846
916
  }
847
- ensureStageSessionFlushed(sessionManager, input.stage.id);
917
+ ensureStageSessionFlushed(sessionManager, runtimeStageId(input));
848
918
  if (!findOpenToolCall(sessionManager)) {
849
- throw new StageSessionReconstructError(`stage session has no open tool call for resume: ${sessionFile}`, { stageId: input.stage.id, sessionFile });
919
+ throw new StageSessionReconstructError(`stage session has no open tool call for resume: ${sessionFile}`, { stageId: runtimeStageId(input), sessionFile });
850
920
  }
851
921
  resumeWaiting = true;
852
922
  }
@@ -856,7 +926,7 @@ export class PiAgentAdapter {
856
926
  let prepareError;
857
927
  const preparePromise = (async () => {
858
928
  if (!sessionManager) {
859
- sessionManager = await createStageSessionManager(input.roots, input.stage.id);
929
+ sessionManager = await createStageSessionManager(input.roots, runtimeStageId(input), sessionFile);
860
930
  }
861
931
  const prepared = await prepareStageSessionWiring(input, sessionManager, askWaitChannel);
862
932
  if (isStageRunResult(prepared)) {
@@ -908,11 +978,11 @@ export class PiAgentAdapter {
908
978
  }
909
979
  };
910
980
  return createConnectedAskWaitStageHandle({
911
- stageId: input.stage.id,
981
+ stageId: runtimeStageId(input),
912
982
  askWaitChannel,
913
983
  onBeforeWaitYield: () => {
914
984
  if (sessionManager) {
915
- ensureStageSessionFlushed(sessionManager, input.stage.id);
985
+ ensureStageSessionFlushed(sessionManager, runtimeStageId(input));
916
986
  }
917
987
  },
918
988
  run: async () => {
@@ -925,7 +995,7 @@ export class PiAgentAdapter {
925
995
  ? {
926
996
  onDeliver: (answer) => {
927
997
  if (!sessionManager) {
928
- throw new StageSessionReconstructError("stage session missing during resume deliver", { stageId: input.stage.id, sessionFile });
998
+ throw new StageSessionReconstructError("stage session missing during resume deliver", { stageId: runtimeStageId(input), sessionFile });
929
999
  }
930
1000
  injectOpaqueAnswerIntoSession(sessionManager, answer);
931
1001
  if (session) {
@@ -935,7 +1005,7 @@ export class PiAgentAdapter {
935
1005
  continueRun: async () => {
936
1006
  return runWithTimeout(async () => {
937
1007
  if (!session || !sessionManager) {
938
- throw new StageSessionReconstructError("stage session missing during resume continue", { stageId: input.stage.id, sessionFile });
1008
+ throw new StageSessionReconstructError("stage session missing during resume continue", { stageId: runtimeStageId(input), sessionFile });
939
1009
  }
940
1010
  syncAgentMessagesFromSession(session, sessionManager);
941
1011
  await session.agent.continue();
@@ -947,7 +1017,7 @@ export class PiAgentAdapter {
947
1017
  await preparePromise.catch(() => undefined);
948
1018
  if (closeOptions?.park) {
949
1019
  if (sessionManager) {
950
- ensureStageSessionFlushed(sessionManager, input.stage.id);
1020
+ ensureStageSessionFlushed(sessionManager, runtimeStageId(input));
951
1021
  if (askWaitChannel.hasPending) {
952
1022
  await repairPrematureAskOperatorClosure(sessionFile);
953
1023
  }
@@ -1,4 +1,5 @@
1
1
  import type { StageEnvelope } from "../types/envelope.js";
2
+ import type { CloneEmitContext, ForkEmitContext } from "../types/forkChoice.js";
2
3
  import type { StageConfig } from "../types/stage.js";
3
4
  import type { TaskFile } from "../types/task.js";
4
5
  import type { StageActivityEvent } from "./activity.js";
@@ -8,14 +9,20 @@ export type StageResumeToken = string;
8
9
  export type StageRunInput = {
9
10
  roots: StageRoots;
10
11
  stage: StageConfig;
12
+ /** Runtime instance id (`work~2`). Defaults to `stage.id` when omitted. */
13
+ stageId?: string;
11
14
  task: TaskFile;
12
15
  priorEnvelope: StageEnvelope | null;
16
+ priorEnvelopes?: StageEnvelope[];
13
17
  timeoutMs?: number;
14
18
  resumeToken?: StageResumeToken;
15
19
  /** Optional observe hook; HITL wait/answer uses openStage beside this. */
16
20
  onActivity?: (event: StageActivityEvent) => void;
17
21
  skillFilePath?: string;
22
+ forkEmitContext?: ForkEmitContext;
23
+ cloneEmitContext?: CloneEmitContext;
18
24
  };
25
+ export declare function runtimeStageId(input: Pick<StageRunInput, "stage" | "stageId">): string;
19
26
  export type StageRunResult = {
20
27
  ok: true;
21
28
  envelope: StageEnvelope;
@@ -1,3 +1,6 @@
1
+ export function runtimeStageId(input) {
2
+ return input.stageId ?? input.stage.id;
3
+ }
1
4
  /** Thin non-HITL wrapper: open → next; unexpected wait fails closed. */
2
5
  export async function runStageViaOpen(port, input) {
3
6
  const handle = port.openStage(input);
@@ -1,5 +1,7 @@
1
1
  import { CredentialSynchronizationError, ModelRuntime, } from "@earendil-works/pi-coding-agent";
2
- import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath, readCredentialSourceFromFile, resolveCredentialBinding, writeCredentialSourceToFile, } from "../runtime/credentialBinding.js";
2
+ import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath, resolveCredentialBinding, writeCredentialSourceToContext, } from "../runtime/credentialBinding.js";
3
+ import { readPersistedCredentialSourceFromContext, } from "../runtime/settingsFile.js";
4
+ import { resolveProjectContext } from "../project/resolveProjectContext.js";
3
5
  export function makeMutationLock() {
4
6
  let tail = Promise.resolve();
5
7
  return {
@@ -245,8 +247,9 @@ export async function logoutProvider(cwd, providerId, ctx = defaultContext) {
245
247
  });
246
248
  }
247
249
  export function detectPiHome(cwd) {
248
- const binding = resolveCredentialBinding(cwd);
249
- const persisted = readCredentialSourceFromFile(cwd);
250
+ const projectCtx = resolveProjectContext(cwd);
251
+ const binding = resolveCredentialBinding(projectCtx);
252
+ const persisted = readPersistedCredentialSourceFromContext(projectCtx);
250
253
  return {
251
254
  piHomeUsable: isUsableAuthFile(piHomeAuthPath()),
252
255
  ...(persisted !== undefined ? { credentialSource: persisted } : {}),
@@ -255,8 +258,9 @@ export function detectPiHome(cwd) {
255
258
  };
256
259
  }
257
260
  export function getCredentialSourceSettings(cwd) {
258
- const persisted = readCredentialSourceFromFile(cwd);
259
- const binding = resolveCredentialBinding(cwd);
261
+ const projectCtx = resolveProjectContext(cwd);
262
+ const persisted = readPersistedCredentialSourceFromContext(projectCtx);
263
+ const binding = resolveCredentialBinding(projectCtx);
260
264
  return {
261
265
  ...(persisted !== undefined ? { credentialSource: persisted } : {}),
262
266
  binding: {
@@ -270,8 +274,9 @@ export function setCredentialSource(cwd, credentialSource) {
270
274
  if (parsed === undefined) {
271
275
  throw new ProviderAuthError('credentialSource must be "pi_home" or "sf_owned"', 400);
272
276
  }
273
- writeCredentialSourceToFile(cwd, parsed);
274
- const binding = resolveCredentialBinding(cwd);
277
+ const projectCtx = resolveProjectContext(cwd);
278
+ writeCredentialSourceToContext(projectCtx, parsed);
279
+ const binding = resolveCredentialBinding(projectCtx);
275
280
  return {
276
281
  credentialSource: parsed,
277
282
  binding: {
@@ -0,0 +1,12 @@
1
+ export declare function displayCatalogPath(absPath: string, projectRoot?: string): string;
2
+ export declare function normalizeCatalogSlashes(pathValue: string): string;
3
+ export type PipelinePathMatchInput = {
4
+ pipeline_id: string;
5
+ pipeline_path?: string;
6
+ project_root?: string;
7
+ };
8
+ export type PipelineListingPath = {
9
+ id: string;
10
+ path: string;
11
+ };
12
+ export declare function matchPipelineRun(run: PipelinePathMatchInput, pipeline: PipelineListingPath): boolean;
@@ -0,0 +1,28 @@
1
+ export function displayCatalogPath(absPath, projectRoot) {
2
+ if (projectRoot) {
3
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/$/, "");
4
+ const normalized = absPath.replace(/\\/g, "/");
5
+ if (normalized === root)
6
+ return ".";
7
+ const prefix = `${root}/`;
8
+ if (normalized.startsWith(prefix)) {
9
+ return normalized.slice(prefix.length);
10
+ }
11
+ }
12
+ const parts = absPath.replace(/\\/g, "/").split("/");
13
+ return parts[parts.length - 1] ?? absPath;
14
+ }
15
+ export function normalizeCatalogSlashes(pathValue) {
16
+ return pathValue.replace(/\\/g, "/");
17
+ }
18
+ export function matchPipelineRun(run, pipeline) {
19
+ if (run.pipeline_id === pipeline.id)
20
+ return true;
21
+ if (run.pipeline_path && run.project_root) {
22
+ const rel = displayCatalogPath(run.pipeline_path, run.project_root);
23
+ if (normalizeCatalogSlashes(rel) === normalizeCatalogSlashes(pipeline.path)) {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
@@ -0,0 +1,10 @@
1
+ export declare const ARTIFACT_USAGE = "Usage:\n sf artifact read --run <runId> --path <relPath> [--out <file>]";
2
+ export type ArtifactCommandIo = {
3
+ log: (line: string) => void;
4
+ error: (line: string) => void;
5
+ };
6
+ export declare function runArtifactCommand(args: string[], options?: {
7
+ cwd?: string;
8
+ projectRoot?: string;
9
+ io?: Partial<ArtifactCommandIo>;
10
+ }): Promise<number>;