@kontourai/flow-agents 3.3.0 → 3.4.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 (256) hide show
  1. package/.github/workflows/add-to-project.yml +15 -0
  2. package/.github/workflows/ci.yml +161 -0
  3. package/CHANGELOG.md +41 -0
  4. package/CONTEXT.md +5 -1
  5. package/README.md +19 -8
  6. package/build/src/builder-flow-run-adapter.d.ts +80 -0
  7. package/build/src/builder-flow-run-adapter.js +241 -0
  8. package/build/src/builder-flow-runtime.d.ts +16 -0
  9. package/build/src/builder-flow-runtime.js +290 -0
  10. package/build/src/cli/builder-run.d.ts +1 -0
  11. package/build/src/cli/builder-run.js +27 -0
  12. package/build/src/cli/effective-backlog-settings.js +70 -2
  13. package/build/src/cli/init.d.ts +34 -0
  14. package/build/src/cli/init.js +341 -61
  15. package/build/src/cli/kit.js +55 -12
  16. package/build/src/cli/pull-work-provider.js +346 -5
  17. package/build/src/cli/skill-drift-check.d.ts +1 -0
  18. package/build/src/cli/skill-drift-check.js +165 -0
  19. package/build/src/cli/telemetry-doctor.d.ts +37 -0
  20. package/build/src/cli/telemetry-doctor.js +53 -6
  21. package/build/src/cli/validate-hook-influence.js +37 -7
  22. package/build/src/cli/workflow-sidecar.d.ts +93 -8
  23. package/build/src/cli/workflow-sidecar.js +1175 -158
  24. package/build/src/cli.js +5 -0
  25. package/build/src/flow-kit/validate.d.ts +54 -34
  26. package/build/src/flow-kit/validate.js +237 -26
  27. package/build/src/index.d.ts +2 -0
  28. package/build/src/index.js +1 -0
  29. package/build/src/lib/console-connect-options.d.ts +97 -0
  30. package/build/src/lib/console-connect-options.js +199 -0
  31. package/build/src/lib/console-telemetry-validate.d.ts +49 -0
  32. package/build/src/lib/console-telemetry-validate.js +91 -0
  33. package/build/src/lib/flow-resolver.d.ts +54 -1
  34. package/build/src/lib/flow-resolver.js +112 -5
  35. package/build/src/lib/fs.d.ts +17 -0
  36. package/build/src/lib/fs.js +172 -0
  37. package/build/src/lib/local-artifact-root.d.ts +44 -1
  38. package/build/src/lib/local-artifact-root.js +131 -3
  39. package/build/src/runtime-adapters.d.ts +39 -3
  40. package/build/src/runtime-adapters.js +77 -31
  41. package/build/src/tools/build-universal-bundles.js +40 -2
  42. package/build/src/tools/codex-agent-routing.d.ts +2 -0
  43. package/build/src/tools/codex-agent-routing.js +49 -0
  44. package/build/src/tools/generate-context-map.js +1 -0
  45. package/build/src/tools/validate-source-tree.js +27 -1
  46. package/context/scripts/hooks/lib/kit-catalog.js +235 -0
  47. package/context/scripts/hooks/lib/runnable-command.js +177 -0
  48. package/context/scripts/hooks/stop-goal-fit.js +278 -48
  49. package/context/scripts/hooks/workflow-steering.js +121 -21
  50. package/context/scripts/package.json +3 -0
  51. package/context/scripts/telemetry/install-console-config.sh +25 -4
  52. package/context/scripts/telemetry/lib/config.sh +102 -12
  53. package/context/scripts/telemetry/lib/pricing.sh +50 -0
  54. package/context/scripts/telemetry/lib/session.sh +3 -0
  55. package/context/scripts/telemetry/lib/transport.sh +87 -0
  56. package/context/scripts/telemetry/lib/usage.sh +205 -4
  57. package/context/scripts/telemetry/telemetry.conf +6 -0
  58. package/context/scripts/telemetry/telemetry.sh +48 -0
  59. package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
  60. package/docs/agent-usage-feedback-loop.md +35 -0
  61. package/docs/architecture-engine-and-kits.md +110 -0
  62. package/docs/context-map.md +2 -0
  63. package/docs/decisions/embeddable-engine.md +152 -0
  64. package/docs/decisions/index.md +3 -1
  65. package/docs/decisions/trust-ledger-retention.md +88 -0
  66. package/docs/decisions/workflow-enforcement.md +31 -9
  67. package/docs/fixture-ownership.md +3 -0
  68. package/docs/implementing-trust-reconciliation.md +129 -0
  69. package/docs/index.md +19 -9
  70. package/docs/integrations/flow-agents-console.md +167 -0
  71. package/docs/kit-authoring-guide.md +52 -21
  72. package/docs/spec/builder-flow-runtime.md +80 -0
  73. package/docs/spec/runtime-hook-surface.md +45 -1
  74. package/docs/specs/economics-record-contract.md +270 -0
  75. package/docs/specs/harness-capability-matrix.md +74 -0
  76. package/docs/specs/learning-review-proposals-contract.md +340 -0
  77. package/docs/specs/routing-efficiency-review.md +59 -0
  78. package/docs/verifiable-trust.md +74 -25
  79. package/docs/workflow-usage-guide.md +10 -0
  80. package/evals/acceptance/prove-capture-teeth.sh +132 -0
  81. package/evals/ci/antigaming-suite.sh +1 -0
  82. package/evals/ci/run-baseline.sh +72 -4
  83. package/evals/fixtures/economics/acceptance.json +12 -0
  84. package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
  85. package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
  86. package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
  87. package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
  88. package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
  89. package/evals/fixtures/economics/critique.json +22 -0
  90. package/evals/fixtures/economics/expected-record.json +71 -0
  91. package/evals/fixtures/economics/session-usage-event.json +1 -0
  92. package/evals/fixtures/economics/state.json +11 -0
  93. package/evals/fixtures/economics/transcript.jsonl +3 -0
  94. package/evals/fixtures/hook-influence/cases.json +7 -7
  95. package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
  96. package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
  97. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
  98. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
  99. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
  100. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
  101. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
  102. package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
  103. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
  104. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
  105. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
  106. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
  107. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
  108. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
  109. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
  110. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
  111. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
  112. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
  113. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
  114. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
  115. package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
  116. package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
  117. package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
  118. package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
  119. package/evals/integration/test_builder_entry_enforcement.sh +241 -0
  120. package/evals/integration/test_builder_step_producers.sh +18 -10
  121. package/evals/integration/test_bundle_install.sh +172 -0
  122. package/evals/integration/test_console_tenant_isolation.sh +167 -0
  123. package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
  124. package/evals/integration/test_dual_emit_flow_step.sh +10 -4
  125. package/evals/integration/test_economics_record.sh +674 -0
  126. package/evals/integration/test_effective_backlog_settings.sh +1 -1
  127. package/evals/integration/test_evidence_capture_hook.sh +17 -2
  128. package/evals/integration/test_exemption_usage_review.sh +198 -0
  129. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  130. package/evals/integration/test_flow_kit_install_git.sh +83 -0
  131. package/evals/integration/test_flowdef_session_activation.sh +0 -1
  132. package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
  133. package/evals/integration/test_gate_lockdown.sh +7 -0
  134. package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
  135. package/evals/integration/test_goal_fit_hook.sh +2031 -0
  136. package/evals/integration/test_hook_category_behaviors.sh +8 -1
  137. package/evals/integration/test_hook_influence_cases.sh +25 -1
  138. package/evals/integration/test_install_merge.sh +227 -2
  139. package/evals/integration/test_kit_conformance_levels.sh +6 -6
  140. package/evals/integration/test_learning_review_proposals.sh +329 -0
  141. package/evals/integration/test_liveness_conflict_injection.sh +26 -22
  142. package/evals/integration/test_liveness_console_relay.sh +166 -0
  143. package/evals/integration/test_liveness_heartbeat.sh +17 -17
  144. package/evals/integration/test_liveness_worktree_root.sh +575 -0
  145. package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
  146. package/evals/integration/test_publish_delivery.sh +331 -1
  147. package/evals/integration/test_pull_work_board.sh +200 -0
  148. package/evals/integration/test_pull_work_provider.sh +1 -1
  149. package/evals/integration/test_record_check.sh +378 -0
  150. package/evals/integration/test_routing_efficiency.sh +71 -0
  151. package/evals/integration/test_runtime_adapter_activation.sh +28 -0
  152. package/evals/integration/test_session_resume_roundtrip.sh +16 -19
  153. package/evals/integration/test_skill_drift_check.sh +870 -0
  154. package/evals/integration/test_telemetry.sh +445 -0
  155. package/evals/integration/test_telemetry_doctor.sh +66 -0
  156. package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
  157. package/evals/integration/test_trust_reconcile_negatives.sh +30 -13
  158. package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
  159. package/evals/integration/test_usage_cost.sh +61 -0
  160. package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
  161. package/evals/integration/test_workflow_steering_hook.sh +157 -16
  162. package/evals/integration/test_workspace_settings.sh +176 -0
  163. package/evals/lib/env.sh +26 -0
  164. package/evals/lib/node.sh +8 -0
  165. package/evals/run.sh +29 -0
  166. package/evals/static/test_ci_integration_coverage.sh +115 -0
  167. package/evals/static/test_declared_scope_forms_documented.sh +114 -0
  168. package/evals/static/test_universal_bundles.sh +34 -0
  169. package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
  170. package/evals/static/test_workflow_skills.sh +1 -1
  171. package/kits/builder/flows/build.flow.json +9 -18
  172. package/kits/builder/flows/publish-learn.flow.json +5 -1
  173. package/kits/builder/kit.json +120 -0
  174. package/kits/builder/skills/deliver/SKILL.md +42 -0
  175. package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
  176. package/kits/builder/skills/execute-plan/SKILL.md +9 -0
  177. package/kits/builder/skills/learning-review/SKILL.md +51 -0
  178. package/kits/builder/skills/plan-work/SKILL.md +17 -20
  179. package/kits/builder/skills/pull-work/SKILL.md +21 -0
  180. package/kits/builder/skills/release-readiness/SKILL.md +12 -0
  181. package/kits/knowledge/kit.json +9 -0
  182. package/kits/veritas-governance/docs/README.md +35 -7
  183. package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
  184. package/kits/veritas-governance/kit.json +14 -0
  185. package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
  186. package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
  187. package/package.json +2 -2
  188. package/packaging/manifest.json +29 -0
  189. package/schemas/backlog-provider-settings.schema.json +13 -0
  190. package/schemas/workflow-state.schema.json +44 -0
  191. package/scripts/README.md +4 -0
  192. package/scripts/check-content-boundary.cjs +8 -1
  193. package/scripts/ci/trust-reconcile.js +136 -0
  194. package/scripts/hooks/codex-hook-adapter.js +77 -2
  195. package/scripts/hooks/evidence-capture.js +38 -5
  196. package/scripts/hooks/lib/codex-exit-code.js +316 -0
  197. package/scripts/hooks/lib/kit-catalog.js +235 -0
  198. package/scripts/hooks/lib/liveness-write.js +28 -1
  199. package/scripts/hooks/lib/local-artifact-paths.js +97 -1
  200. package/scripts/hooks/lib/runnable-command.js +177 -0
  201. package/scripts/hooks/lib/skill-drift.js +350 -0
  202. package/scripts/hooks/stop-goal-fit.js +278 -48
  203. package/scripts/hooks/workflow-steering.js +121 -21
  204. package/scripts/install-codex-home.sh +97 -47
  205. package/scripts/install-merge.js +72 -14
  206. package/scripts/install-owned-files.js +178 -0
  207. package/scripts/liveness/relay.sh +84 -0
  208. package/scripts/telemetry/economics-record.schema.json +145 -0
  209. package/scripts/telemetry/economics-record.sh +331 -0
  210. package/scripts/telemetry/install-console-config.sh +25 -4
  211. package/scripts/telemetry/learning-review-decide.sh +124 -0
  212. package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
  213. package/scripts/telemetry/learning-review-proposals.sh +484 -0
  214. package/scripts/telemetry/lib/config.sh +102 -12
  215. package/scripts/telemetry/lib/pricing.sh +14 -6
  216. package/scripts/telemetry/lib/session.sh +3 -0
  217. package/scripts/telemetry/lib/transport.sh +133 -15
  218. package/scripts/telemetry/lib/usage.sh +121 -28
  219. package/scripts/telemetry/routing-efficiency.sh +0 -0
  220. package/scripts/telemetry/telemetry.conf +6 -0
  221. package/scripts/telemetry/telemetry.sh +48 -0
  222. package/src/builder-flow-run-adapter.ts +357 -0
  223. package/src/builder-flow-runtime.ts +348 -0
  224. package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
  225. package/src/cli/builder-flow-runtime.test.mjs +213 -0
  226. package/src/cli/builder-run.ts +28 -0
  227. package/src/cli/codex-agent-routing.test.mjs +44 -0
  228. package/src/cli/codex-exit-code.test.mjs +207 -0
  229. package/src/cli/console-connect-options.test.mjs +329 -0
  230. package/src/cli/console-telemetry-validate.test.mjs +157 -0
  231. package/src/cli/effective-backlog-settings.ts +68 -2
  232. package/src/cli/flow-resolver-composition.test.mjs +72 -0
  233. package/src/cli/init.test.mjs +161 -0
  234. package/src/cli/init.ts +407 -62
  235. package/src/cli/kit-metadata-security.test.mjs +443 -0
  236. package/src/cli/kit.ts +50 -12
  237. package/src/cli/pull-work-provider.ts +377 -3
  238. package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
  239. package/src/cli/skill-drift-check.ts +196 -0
  240. package/src/cli/telemetry-doctor.test.mjs +53 -0
  241. package/src/cli/telemetry-doctor.ts +50 -7
  242. package/src/cli/validate-hook-influence.ts +37 -6
  243. package/src/cli/workflow-sidecar.ts +1150 -151
  244. package/src/cli.ts +5 -0
  245. package/src/flow-kit/validate.ts +277 -38
  246. package/src/index.ts +19 -0
  247. package/src/lib/console-connect-options.ts +261 -0
  248. package/src/lib/console-telemetry-validate.ts +88 -0
  249. package/src/lib/flow-resolver.ts +117 -4
  250. package/src/lib/fs.ts +160 -0
  251. package/src/lib/local-artifact-root.ts +129 -3
  252. package/src/runtime-adapters.ts +113 -33
  253. package/src/tools/build-universal-bundles.ts +36 -2
  254. package/src/tools/codex-agent-routing.ts +48 -0
  255. package/src/tools/generate-context-map.ts +1 -0
  256. package/src/tools/validate-source-tree.ts +26 -1
@@ -0,0 +1,348 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import {
5
+ expectationsForGate,
6
+ openGates,
7
+ readJson,
8
+ runDir,
9
+ sha256File,
10
+ type FlowGate,
11
+ type FlowExpectation,
12
+ type FlowRunState,
13
+ type JsonObject,
14
+ } from "@kontourai/flow";
15
+ import {
16
+ BUILDER_BUILD_FLOW_ID,
17
+ BuilderBuildRunInputError,
18
+ evaluateBuilderBuildRun,
19
+ loadBuilderBuildRun,
20
+ startBuilderBuildRun,
21
+ type BuilderBuildRunResult,
22
+ } from "./builder-flow-run-adapter.js";
23
+
24
+ type AnyRecord = Record<string, any>;
25
+
26
+ export interface BuilderFlowSessionInput {
27
+ sessionDir: string;
28
+ }
29
+
30
+ export interface BuilderFlowSessionResult {
31
+ sessionDir: string;
32
+ projectRoot: string;
33
+ run: BuilderBuildRunResult;
34
+ projection: AnyRecord;
35
+ attached: boolean;
36
+ }
37
+
38
+ type SessionContext = {
39
+ sessionDir: string;
40
+ artifactRoot: string;
41
+ projectRoot: string;
42
+ slug: string;
43
+ stateFile: string;
44
+ bundleFile: string;
45
+ };
46
+
47
+ export async function startBuilderFlowSession(input: BuilderFlowSessionInput): Promise<BuilderFlowSessionResult> {
48
+ const context = resolveSessionContext(input.sessionDir);
49
+ const sidecarState = readSidecarState(context);
50
+ const subject = workflowSubject(sidecarState);
51
+ let run: BuilderBuildRunResult;
52
+ try {
53
+ run = await loadBuilderBuildRun({
54
+ cwd: context.projectRoot,
55
+ runId: context.slug,
56
+ });
57
+ } catch (error) {
58
+ if (!isRunNotFound(error)) throw error;
59
+ run = await startBuilderBuildRun({
60
+ cwd: context.projectRoot,
61
+ runId: context.slug,
62
+ subject,
63
+ params: {
64
+ subject,
65
+ },
66
+ });
67
+ }
68
+ return syncAndProject(context, run);
69
+ }
70
+
71
+ export async function syncBuilderFlowSession(input: BuilderFlowSessionInput): Promise<BuilderFlowSessionResult> {
72
+ const context = resolveSessionContext(input.sessionDir);
73
+ const run = await loadBuilderBuildRun({
74
+ cwd: context.projectRoot,
75
+ runId: context.slug,
76
+ });
77
+ return syncAndProject(context, run);
78
+ }
79
+
80
+ export async function syncBuilderFlowSessionIfPresent(sessionDir: string): Promise<BuilderFlowSessionResult | null> {
81
+ let context: SessionContext;
82
+ try {
83
+ context = resolveSessionContext(sessionDir);
84
+ } catch (error) {
85
+ if (error instanceof BuilderBuildRunInputError && error.field === "sessionDir") return null;
86
+ throw error;
87
+ }
88
+ if (!fs.existsSync(runDir(context.slug, context.projectRoot))) return null;
89
+ return syncBuilderFlowSession({ sessionDir });
90
+ }
91
+
92
+ async function syncAndProject(context: SessionContext, initial: BuilderBuildRunResult): Promise<BuilderFlowSessionResult> {
93
+ let run = initial;
94
+ let attached = false;
95
+ const gates = openGatesForResult(run);
96
+ if (run.state.status === "active" && gates.length !== 1) {
97
+ throw new BuilderBuildRunInputError("flow_run.open_gates", `expected exactly one gate for active step ${run.state.current_step}, found ${gates.length}`);
98
+ }
99
+ if (gates.length === 1 && fs.existsSync(context.bundleFile)) {
100
+ const rawBundle = await readJson(context.bundleFile);
101
+ const gateEvidence = bundleGateEvidence(rawBundle, gates[0]!, run.state.subject);
102
+ if (gateEvidence) {
103
+ const digest = await sha256File(context.bundleFile);
104
+ const alreadyAttached = manifestEvidence(run.manifest).some((entry) =>
105
+ entry.gate_id === gates[0]!.id && entry.sha256 === digest
106
+ );
107
+ if (!alreadyAttached) {
108
+ run = await evaluateBuilderBuildRun({
109
+ cwd: context.projectRoot,
110
+ runId: context.slug,
111
+ evidence: {
112
+ gate: gates[0]!.id,
113
+ file: path.relative(context.projectRoot, context.bundleFile),
114
+ ...(gateEvidence.failed ? { status: "failed" } : {}),
115
+ ...(gateEvidence.routeReason ? { routeReason: gateEvidence.routeReason } : {}),
116
+ },
117
+ });
118
+ attached = true;
119
+ }
120
+ }
121
+ }
122
+ const projection = projectFlowRun(context, run);
123
+ writeProjection(context, projection);
124
+ return {
125
+ sessionDir: context.sessionDir,
126
+ projectRoot: context.projectRoot,
127
+ run,
128
+ projection,
129
+ attached,
130
+ };
131
+ }
132
+
133
+ function resolveSessionContext(sessionDirInput: string): SessionContext {
134
+ const sessionDir = path.resolve(sessionDirInput);
135
+ const artifactRoot = path.dirname(sessionDir);
136
+ const kontouraiRoot = path.dirname(artifactRoot);
137
+ if (path.basename(artifactRoot) !== "flow-agents" || path.basename(kontouraiRoot) !== ".kontourai") {
138
+ throw new BuilderBuildRunInputError("sessionDir", "must be .kontourai/flow-agents/<slug>");
139
+ }
140
+ const slug = path.basename(sessionDir);
141
+ if (!slug || slug === "." || slug === "..") {
142
+ throw new BuilderBuildRunInputError("sessionDir", "must name a session");
143
+ }
144
+ const stateFile = path.join(sessionDir, "state.json");
145
+ if (!fs.existsSync(stateFile)) {
146
+ throw new BuilderBuildRunInputError("sessionDir", "must contain state.json");
147
+ }
148
+ return {
149
+ sessionDir,
150
+ artifactRoot,
151
+ projectRoot: path.dirname(kontouraiRoot),
152
+ slug,
153
+ stateFile,
154
+ bundleFile: path.join(sessionDir, "trust.bundle"),
155
+ };
156
+ }
157
+
158
+ function readSidecarState(context: SessionContext): AnyRecord {
159
+ const value = JSON.parse(fs.readFileSync(context.stateFile, "utf8"));
160
+ if (!isRecord(value) || value.task_slug !== context.slug) {
161
+ throw new BuilderBuildRunInputError("sessionDir", "state.json task_slug must match the session directory");
162
+ }
163
+ return value;
164
+ }
165
+
166
+ function workflowSubject(state: AnyRecord): string {
167
+ const refs = Array.isArray(state.work_item_refs)
168
+ ? state.work_item_refs.filter((value: unknown): value is string => typeof value === "string" && value.length > 0)
169
+ : [];
170
+ if (refs.length !== 1) {
171
+ throw new BuilderBuildRunInputError("state.work_item_refs", "must contain exactly one selected Work Item for builder.build");
172
+ }
173
+ return refs[0]!;
174
+ }
175
+
176
+ function openGatesForResult(run: BuilderBuildRunResult): Array<FlowGate & { id: string }> {
177
+ return openGates(
178
+ JSON.parse(fs.readFileSync(path.join(run.dir, "definition.json"), "utf8")),
179
+ run.state,
180
+ ) as Array<FlowGate & { id: string }>;
181
+ }
182
+
183
+ function bundleGateEvidence(bundle: unknown, gate: FlowGate, subject: string): { failed: boolean; routeReason: string | null } | null {
184
+ if (!isRecord(bundle) || !Array.isArray(bundle.claims)) return null;
185
+ const selectors = (expectationsForGate(gate) as FlowExpectation[]).map((expectation) => expectation.bundle_claim);
186
+ const relevant = bundle.claims.filter((claim: unknown): claim is AnyRecord => {
187
+ if (!isRecord(claim)) return false;
188
+ return selectors.some((candidate: FlowExpectation["bundle_claim"]) =>
189
+ candidate.claimType === claim.claimType
190
+ && (!candidate.subjectType || candidate.subjectType === claim.subjectType)
191
+ );
192
+ });
193
+ if (relevant.length === 0) return null;
194
+ if (relevant.some((claim) => workflowSubjectRef(claim) !== subject)) {
195
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.workflow_subject_ref", "must match the persisted run subject");
196
+ }
197
+ const failed = relevant.some((claim) => claim.value === "fail" || claim.status === "disputed");
198
+ const routeReasons = [...new Set(relevant.flatMap((claim) => {
199
+ const metadata = isRecord(claim.metadata) ? claim.metadata : null;
200
+ const gateClaim = metadata && isRecord(metadata.gate_claim) ? metadata.gate_claim : null;
201
+ return gateClaim && typeof gateClaim.route_reason === "string" ? [gateClaim.route_reason] : [];
202
+ }))];
203
+ if (routeReasons.length > 1) {
204
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.gate_claim.route_reason", "must agree across current-gate claims");
205
+ }
206
+ const routeReason = routeReasons[0] ?? null;
207
+ if (routeReason && !failed) {
208
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.gate_claim.route_reason", "requires failed current-gate evidence");
209
+ }
210
+ const routeMap = isRecord((gate as AnyRecord).on_route_back) ? (gate as AnyRecord).on_route_back : null;
211
+ if (routeReason && (!routeMap || typeof routeMap[routeReason] !== "string")) {
212
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.gate_claim.route_reason", `is not declared by gate ${String((gate as AnyRecord).id ?? "<unknown>")}`);
213
+ }
214
+ return { failed, routeReason };
215
+ }
216
+
217
+ function workflowSubjectRef(claim: AnyRecord): string | null {
218
+ const metadata = isRecord(claim.metadata) ? claim.metadata : null;
219
+ return metadata && typeof metadata.workflow_subject_ref === "string"
220
+ ? metadata.workflow_subject_ref
221
+ : null;
222
+ }
223
+
224
+ function manifestEvidence(manifest: JsonObject): AnyRecord[] {
225
+ return Array.isArray(manifest.evidence) ? manifest.evidence.filter(isRecord) : [];
226
+ }
227
+
228
+ function projectFlowRun(context: SessionContext, run: BuilderBuildRunResult): AnyRecord {
229
+ const sidecar = readSidecarState(context);
230
+ const definition = JSON.parse(fs.readFileSync(path.join(run.dir, "definition.json"), "utf8"));
231
+ const gates = openGates(definition, run.state) as Array<FlowGate & { id: string }>;
232
+ const complete = run.state.status === "completed";
233
+ const action = complete ? { skills: [], operations: [] } : stepAction(run.state.current_step);
234
+ if (!action) {
235
+ throw new BuilderBuildRunInputError("kit.flow_step_actions", `does not declare Builder step ${run.state.current_step}`);
236
+ }
237
+ const required = gates.flatMap((gate) => (expectationsForGate(gate) as FlowExpectation[])
238
+ .filter((expectation: FlowExpectation) => expectation.required)
239
+ .map((expectation: FlowExpectation) => `${expectation.id} (${expectation.bundle_claim.claimType}/${expectation.bundle_claim.subjectType ?? "any"})`));
240
+ const skills = action?.skills ?? [];
241
+ const operations = action?.operations ?? [];
242
+ const syncCommand = `flow-agents builder-run sync --session-dir .kontourai/flow-agents/${context.slug}`;
243
+ const routeBack = latestRouteBack(run.state);
244
+ const skillText = skills.length ? `Activate ${skills.map((skill) => `\`${skill}\``).join(" then ")}.` : "No Builder skill is required.";
245
+ const operationText = operations.length ? ` Perform ${operations.map((operation) => `\`${operation}\``).join(" then ")}.` : "";
246
+ const gateText = gates.length
247
+ ? `Complete ${gates.map((gate) => `\`${gate.id}\``).join(", ")} by recording: ${required.join(", ") || "its declared evidence"}.`
248
+ : "No Flow gate is open.";
249
+ const routeText = routeBack
250
+ ? ` Route-back history: attempt ${routeBack.attempt ?? "n/a"}${routeBack.max_attempts ? `/${routeBack.max_attempts}` : ""} returned to \`${routeBack.route_back_to ?? "an earlier step"}\`${routeBack.route_reason ? ` for \`${routeBack.route_reason}\`` : ""}.`
251
+ : "";
252
+ const nextAction = complete
253
+ ? { status: "done", summary: "Canonical Flow run is complete." }
254
+ : {
255
+ status: "continue",
256
+ summary: `Flow step \`${run.state.current_step}\`: ${skillText}${operationText} ${gateText}${routeText} Then synchronize the recorded evidence.`,
257
+ skills,
258
+ operations,
259
+ command: syncCommand,
260
+ };
261
+ const phase = phaseForStep(definition.phase_map, run.state.current_step) ?? sidecar.phase;
262
+ return {
263
+ ...sidecar,
264
+ status: complete ? "delivered" : (run.state.transitions.length > 0 ? "in_progress" : sidecar.status),
265
+ phase: complete ? "done" : phase,
266
+ updated_at: run.state.updated_at,
267
+ flow_run: {
268
+ run_id: run.runId,
269
+ definition_id: run.definitionId,
270
+ definition_version: run.definitionVersion,
271
+ status: run.state.status,
272
+ current_step: run.state.current_step,
273
+ run_ref: path.relative(context.projectRoot, run.dir),
274
+ open_gate_ids: gates.map((gate) => gate.id),
275
+ ...(typeof routeBack?.attempt === "number" ? { route_back_attempt: routeBack.attempt } : {}),
276
+ ...(typeof routeBack?.max_attempts === "number" ? { route_back_max_attempts: routeBack.max_attempts } : {}),
277
+ },
278
+ next_action: nextAction,
279
+ };
280
+ }
281
+
282
+ function writeProjection(context: SessionContext, projection: AnyRecord): void {
283
+ fs.writeFileSync(context.stateFile, `${JSON.stringify(projection, null, 2)}\n`);
284
+ const pointerFiles = [path.join(context.artifactRoot, "current.json")];
285
+ const actorRoot = path.join(context.artifactRoot, "current");
286
+ if (fs.existsSync(actorRoot)) {
287
+ pointerFiles.push(...fs.readdirSync(actorRoot)
288
+ .filter((name) => name.endsWith(".json"))
289
+ .map((name) => path.join(actorRoot, name)));
290
+ }
291
+ for (const file of pointerFiles) {
292
+ if (!fs.existsSync(file)) continue;
293
+ const pointer = JSON.parse(fs.readFileSync(file, "utf8"));
294
+ if (!isRecord(pointer) || pointer.active_slug !== context.slug) continue;
295
+ pointer.active_flow_id = BUILDER_BUILD_FLOW_ID;
296
+ pointer.active_step_id = projection.flow_run.current_step;
297
+ pointer.updated_at = projection.updated_at;
298
+ fs.writeFileSync(file, `${JSON.stringify(pointer, null, 2)}\n`);
299
+ }
300
+ }
301
+
302
+ function stepAction(stepId: string): { skills: string[]; operations: string[] } | null {
303
+ const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot(), "kits", "builder", "kit.json"), "utf8"));
304
+ const actions = Array.isArray(manifest.flow_step_actions) ? manifest.flow_step_actions : [];
305
+ const action = actions.find((candidate: unknown) =>
306
+ isRecord(candidate)
307
+ && candidate.flow_id === BUILDER_BUILD_FLOW_ID
308
+ && candidate.step_id === stepId
309
+ );
310
+ if (!isRecord(action) || !Array.isArray(action.skills) || !action.skills.every((skill: unknown) => typeof skill === "string")) {
311
+ return null;
312
+ }
313
+ const operations = Array.isArray(action.operations) && action.operations.every((operation: unknown) => typeof operation === "string")
314
+ ? action.operations
315
+ : [];
316
+ return { skills: action.skills, operations };
317
+ }
318
+
319
+ function phaseForStep(phaseMap: unknown, stepId: string): string | null {
320
+ if (!isRecord(phaseMap)) return stepId === "design-probe" ? "pickup" : null;
321
+ return Object.entries(phaseMap).find(([, step]) => step === stepId)?.[0] ?? (stepId === "design-probe" ? "pickup" : null);
322
+ }
323
+
324
+ function latestRouteBack(state: FlowRunState): AnyRecord | null {
325
+ const outcomes = Array.isArray(state.gate_outcomes) ? state.gate_outcomes : [];
326
+ return [...outcomes].reverse().find((outcome) => isRecord(outcome) && outcome.status === "route-back") ?? null;
327
+ }
328
+
329
+ function packageRoot(): string {
330
+ let directory = path.dirname(fileURLToPath(import.meta.url));
331
+ while (!fs.existsSync(path.join(directory, "package.json"))) {
332
+ const parent = path.dirname(directory);
333
+ if (parent === directory) throw new Error("unable to locate Flow Agents package root");
334
+ directory = parent;
335
+ }
336
+ return directory;
337
+ }
338
+
339
+ function isRunNotFound(error: unknown): boolean {
340
+ return isRecord(error) && (
341
+ error.code === "flow.run_location.not_found"
342
+ || (typeof error.message === "string" && error.message.includes("flow.run_location.not_found"))
343
+ );
344
+ }
345
+
346
+ function isRecord(value: unknown): value is AnyRecord {
347
+ return typeof value === "object" && value !== null && !Array.isArray(value);
348
+ }