@kontourai/flow-agents 3.3.0 → 3.4.1

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 +48 -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 +56 -3
  34. package/build/src/lib/flow-resolver.js +151 -11
  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 +101 -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 +153 -10
  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,178 @@
1
+ #!/usr/bin/env node
2
+ // Install a prepared Flow Agents overlay without claiming or deleting unrelated files.
3
+ const crypto = require("node:crypto");
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
6
+
7
+ function fail(message) {
8
+ process.stderr.write(`install-owned-files: ${message}\n`);
9
+ process.exit(1);
10
+ }
11
+
12
+ const [sourceArg, destArg, manifestArg] = process.argv.slice(2);
13
+ if (!sourceArg || !destArg || !manifestArg) fail("usage: install-owned-files.js <overlay> <destination> <manifest-relative-path>");
14
+ const source = fs.realpathSync(sourceArg);
15
+ const dest = fs.realpathSync(destArg);
16
+ const manifestPath = path.join(dest, manifestArg);
17
+
18
+ function hashFile(file) {
19
+ return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
20
+ }
21
+
22
+ function relativeFiles(root, current = root) {
23
+ const out = [];
24
+ for (const name of fs.readdirSync(current).sort()) {
25
+ const file = path.join(current, name);
26
+ const stat = fs.lstatSync(file);
27
+ if (stat.isSymbolicLink()) fail(`refusing symlink in install overlay: ${file}`);
28
+ if (stat.isDirectory()) out.push(...relativeFiles(root, file));
29
+ else if (stat.isFile()) out.push(path.relative(root, file).split(path.sep).join("/"));
30
+ else fail(`unsupported install overlay entry: ${file}`);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ function targetFor(rel) {
36
+ if (!rel || rel.split("/").some((part) => !part || part === "." || part === "..")) fail(`unsafe relative path: ${rel}`);
37
+ const target = path.resolve(dest, ...rel.split("/"));
38
+ const relative = path.relative(dest, target);
39
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) fail(`path escapes destination: ${rel}`);
40
+ return target;
41
+ }
42
+
43
+ function ensureSafeParent(target, create) {
44
+ const relative = path.relative(dest, path.dirname(target));
45
+ let current = dest;
46
+ for (const part of relative.split(path.sep).filter(Boolean)) {
47
+ current = path.join(current, part);
48
+ if (fs.existsSync(current)) {
49
+ const stat = fs.lstatSync(current);
50
+ if (stat.isSymbolicLink()) fail(`refusing to write through symlink: ${current}`);
51
+ if (!stat.isDirectory()) fail(`destination component is not a directory: ${current}`);
52
+ } else if (create) {
53
+ fs.mkdirSync(current);
54
+ }
55
+ }
56
+ }
57
+
58
+ function readManifest() {
59
+ if (!fs.existsSync(manifestPath)) return new Map();
60
+ ensureSafeParent(manifestPath, false);
61
+ const stat = fs.lstatSync(manifestPath);
62
+ if (stat.isSymbolicLink() || !stat.isFile()) fail(`ownership manifest is not a regular file: ${manifestPath}`);
63
+ let parsed;
64
+ try { parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8")); } catch (error) { fail(`invalid ownership manifest: ${error.message}`); }
65
+ if (parsed.schema_version !== "1.0" || !Array.isArray(parsed.files)) fail("unsupported ownership manifest; remove it only after manually auditing installed files");
66
+ return new Map(parsed.files.map((entry) => [String(entry.path), String(entry.sha256)]));
67
+ }
68
+
69
+ function legacyInstallCutoff() {
70
+ if (fs.existsSync(manifestPath)) return null;
71
+ const recordPath = path.join(dest, ".flow-agents", "install.json");
72
+ const hooksPath = path.join(dest, "hooks.json");
73
+ for (const file of [recordPath, hooksPath]) {
74
+ if (!fs.existsSync(file)) return null;
75
+ ensureSafeParent(file, false);
76
+ const stat = fs.lstatSync(file);
77
+ if (stat.isSymbolicLink() || !stat.isFile()) return null;
78
+ }
79
+ let record;
80
+ try { record = JSON.parse(fs.readFileSync(recordPath, "utf8")); } catch { return null; }
81
+ const installedAt = Date.parse(String(record.installedAt ?? ""));
82
+ if (record.runtime !== "codex" || !/^\d+\.\d+\.\d+(?:[-+].*)?$/.test(String(record.version ?? "")) || !Number.isFinite(installedAt)) return null;
83
+ const hooks = fs.readFileSync(hooksPath, "utf8");
84
+ if (!hooks.includes("Recording Flow Agents telemetry") && !hooks.includes("stop-goal-fit.js")) return null;
85
+ return installedAt + 2000;
86
+ }
87
+
88
+ // Exact path classes managed by the pre-manifest installer. These are an
89
+ // auditable historical contract, intentionally narrower than "all incoming
90
+ // files": root config/auth/hooks, kits/local, user-extensible skills/agents,
91
+ // and arbitrary destination files are never bootstrapped from timestamps.
92
+ function wasLegacyManagedPath(rel) {
93
+ const [top] = rel.split("/");
94
+ const managedTrees = new Set([
95
+ ".flow-agents", "agent-cards", "build", "context", "docs", "evals",
96
+ "integrations", "packaging", "powers", "prompts", "schemas", "scripts",
97
+ ]);
98
+ if (managedTrees.has(top)) return rel !== ".flow-agents/install.json";
99
+ if (top === "kits") return !rel.startsWith("kits/local/");
100
+ return new Set(["README.md", "console.telemetry.json", "install.sh"]).has(rel);
101
+ }
102
+
103
+ const previous = readManifest();
104
+ const legacyCutoff = legacyInstallCutoff();
105
+ const incoming = relativeFiles(source).map((rel) => ({ rel, source: path.join(source, ...rel.split("/")), hash: hashFile(path.join(source, ...rel.split("/"))) }));
106
+
107
+ // Complete preflight before the first destination mutation.
108
+ for (const entry of incoming) {
109
+ const target = targetFor(entry.rel);
110
+ ensureSafeParent(target, false);
111
+ if (!fs.existsSync(target)) continue;
112
+ const stat = fs.lstatSync(target);
113
+ if (stat.isSymbolicLink()) fail(`refusing to replace symlink: ${target}`);
114
+ if (!stat.isFile()) fail(`destination collision is not a regular file: ${target}`);
115
+ const oldHash = previous.get(entry.rel);
116
+ const currentHash = hashFile(target);
117
+ // Migration from the pre-manifest installer: byte-identical bundle files can
118
+ // be adopted without overwriting them. Any differing unowned file is a hard
119
+ // collision and remains untouched.
120
+ if (!oldHash) {
121
+ if (currentHash !== entry.hash) {
122
+ const recognizableLegacy = legacyCutoff !== null && wasLegacyManagedPath(entry.rel) && stat.mtimeMs <= legacyCutoff;
123
+ if (!recognizableLegacy) fail(`refusing to overwrite unowned or ambiguous file: ${target}`);
124
+ }
125
+ } else if (currentHash !== oldHash && currentHash !== entry.hash) {
126
+ fail(`refusing to overwrite modified Flow Agents file: ${target}`);
127
+ }
128
+ }
129
+ for (const [rel] of previous) {
130
+ const target = targetFor(rel);
131
+ ensureSafeParent(target, false);
132
+ if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) fail(`refusing to remove symlink replacing owned file: ${target}`);
133
+ }
134
+
135
+ for (const entry of incoming) {
136
+ const target = targetFor(entry.rel);
137
+ ensureSafeParent(target, true);
138
+ const temp = path.join(path.dirname(target), `.${path.basename(target)}.flow-agents-${process.pid}-${crypto.randomBytes(6).toString("hex")}.tmp`);
139
+ try {
140
+ fs.copyFileSync(entry.source, temp, fs.constants.COPYFILE_EXCL);
141
+ fs.chmodSync(temp, fs.statSync(entry.source).mode & 0o777);
142
+ fs.renameSync(temp, target);
143
+ } finally {
144
+ fs.rmSync(temp, { force: true });
145
+ }
146
+ }
147
+
148
+ const incomingPaths = new Set(incoming.map((entry) => entry.rel));
149
+ const staleParents = new Set();
150
+ for (const [rel, oldHash] of previous) {
151
+ if (incomingPaths.has(rel)) continue;
152
+ const target = targetFor(rel);
153
+ if (!fs.existsSync(target)) continue;
154
+ const stat = fs.lstatSync(target);
155
+ if (!stat.isFile() || hashFile(target) !== oldHash) {
156
+ process.stderr.write(`install-owned-files: preserving modified former Flow Agents file: ${target}\n`);
157
+ continue;
158
+ }
159
+ fs.rmSync(target);
160
+ staleParents.add(path.dirname(target));
161
+ }
162
+ for (const start of [...staleParents].sort((a, b) => b.length - a.length)) {
163
+ let current = start;
164
+ while (current !== dest) {
165
+ try { fs.rmdirSync(current); } catch { break; }
166
+ current = path.dirname(current);
167
+ }
168
+ }
169
+
170
+ ensureSafeParent(manifestPath, true);
171
+ if (fs.existsSync(manifestPath) && fs.lstatSync(manifestPath).isSymbolicLink()) fail(`refusing to replace symlink: ${manifestPath}`);
172
+ const manifestTemp = `${manifestPath}.tmp.${process.pid}.${crypto.randomBytes(6).toString("hex")}`;
173
+ try {
174
+ fs.writeFileSync(manifestTemp, `${JSON.stringify({ schema_version: "1.0", files: incoming.map((entry) => ({ path: entry.rel, sha256: entry.hash })) }, null, 2)}\n`, { flag: "wx" });
175
+ fs.renameSync(manifestTemp, manifestPath);
176
+ } finally {
177
+ fs.rmSync(manifestTemp, { force: true });
178
+ }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ # relay.sh — OPTIONAL console liveness relay (#295, ADR 0021 §4/§7).
3
+ #
4
+ # Mirrors a single liveness event (claim/heartbeat/release) to the hosted Console as a
5
+ # `kontour.console.liveness` record, reusing the telemetry transport's shared `console_post_json`
6
+ # (endpoint-allow gate, Bearer + tenant auth, timeouts, temp-file handling, detached fire — one core,
7
+ # never forked). The Console side ingests this record type and projects the fleet view + runs the
8
+ # janitor (console repo #125); this script is only the flow-agents EMIT half.
9
+ #
10
+ # STRICTLY OPTIONAL and local-first (ADR 0012 §5): a no-op unless FLOW_AGENTS_CONSOLE_LIVENESS_RELAY
11
+ # is enabled AND a console endpoint is configured. Best-effort throughout — it must NEVER block, slow,
12
+ # or fail the local liveness write that already happened before this was invoked. Every failure path
13
+ # is a quiet `exit 0`.
14
+ #
15
+ # Invoked (fully detached, best-effort) from scripts/hooks/lib/liveness-write.js after the durable
16
+ # local append. Usage: relay.sh '<liveness-event-json>'
17
+ set -uo pipefail
18
+
19
+ # Opt-in gate — off by default.
20
+ case "${FLOW_AGENTS_CONSOLE_LIVENESS_RELAY:-}" in
21
+ 1 | true | TRUE | yes | on) ;;
22
+ *) exit 0 ;;
23
+ esac
24
+
25
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit 0
26
+
27
+ # transport.sh sources ${TELEMETRY_DIR}/lib/redact.sh at load — point it at the telemetry lib dir,
28
+ # then reuse the SAME console POST core the telemetry mirror uses.
29
+ export TELEMETRY_DIR="${TELEMETRY_DIR:-$SCRIPT_DIR/../telemetry}"
30
+ [[ -f "$TELEMETRY_DIR/lib/transport.sh" ]] || exit 0
31
+ # shellcheck source=/dev/null
32
+ source "$TELEMETRY_DIR/lib/transport.sh" 2>/dev/null || exit 0
33
+
34
+ # Resolve the liveness records endpoint: an explicit override wins, else the base console URL + /records
35
+ # ("POST /records with a liveness record type"). No console configured ⇒ no-op.
36
+ endpoint="${FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL:-}"
37
+ if [[ -z "$endpoint" ]]; then
38
+ base="${FLOW_AGENTS_CONSOLE_URL:-${CONSOLE_TELEMETRY_URL:-${CONSOLE_URL:-}}}"
39
+ [[ -z "$base" ]] && exit 0
40
+ endpoint="${base%/}/records"
41
+ fi
42
+
43
+ # Auth reuses the env names console_post_json reads (CONSOLE_TELEMETRY_TOKEN / CONSOLE_TENANT_ID),
44
+ # accepting FLOW_AGENTS_CONSOLE_* aliases and an optional token file. Never fatal on a missing/bad
45
+ # token — console_post_json only adds a header when the value passes its safety check.
46
+ if [[ -z "${CONSOLE_TELEMETRY_TOKEN:-}" ]]; then
47
+ token_file="${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-${CONSOLE_TELEMETRY_TOKEN_FILE:-}}"
48
+ if [[ -n "$token_file" && -r "$token_file" ]]; then
49
+ CONSOLE_TELEMETRY_TOKEN="$(tr -d '\r\n' < "$token_file" 2>/dev/null || true)"
50
+ export CONSOLE_TELEMETRY_TOKEN
51
+ fi
52
+ fi
53
+ if [[ -z "${CONSOLE_TENANT_ID:-}" && -n "${FLOW_AGENTS_CONSOLE_TENANT:-}" ]]; then
54
+ export CONSOLE_TENANT_ID="$FLOW_AGENTS_CONSOLE_TENANT"
55
+ fi
56
+
57
+ # The liveness event arrives as a single JSON argument.
58
+ event_json="${1:-}"
59
+ [[ -z "$event_json" ]] && exit 0
60
+
61
+ # Build the kontour.console.liveness record with jq — guarantees valid JSON and, critically,
62
+ # proper string escaping of every untrusted field (actor/subjectId/branch/artifact_dir), so hostile
63
+ # control bytes are \u-escaped rather than emitted raw (injection discipline). No jq ⇒ no-op.
64
+ command -v jq >/dev/null 2>&1 || exit 0
65
+ record="$(printf '%s' "$event_json" | jq -c '{
66
+ schema: "kontour.console.liveness",
67
+ version: "0.1",
68
+ type: (.type // "claim"),
69
+ subjectId: .subjectId,
70
+ actor: .actor,
71
+ actor_key: (.actor_key // .actorKey // null),
72
+ at: .at,
73
+ ttlSeconds: (.ttlSeconds // null),
74
+ host: (.host // null),
75
+ branch: (.branch // null),
76
+ artifact_dir: (.artifact_dir // .artifactDir // null)
77
+ }' 2>/dev/null)" || exit 0
78
+ [[ -z "$record" || "$record" == "null" ]] && exit 0
79
+
80
+ # Optional field redaction (default none — jq escaping already neutralizes injection), then the
81
+ # shared best-effort POST. console_post_json enforces the https/localhost endpoint-allow gate.
82
+ processed="$(redact_event "$record" "${FLOW_AGENTS_CONSOLE_LIVENESS_REDACT:-none}")"
83
+ console_post_json "$endpoint" "$processed"
84
+ exit 0
@@ -0,0 +1,145 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://kontourai.dev/schemas/kontour.console.economics/0.1.json",
4
+ "title": "kontour.console.economics",
5
+ "description": "Per-run kit-economics record (flow-agents #349, console ADR 0003). Immutable per-run fact: cost, time, iterations, and defects caught. cost and defects are co-required (R7 Goodhart guard).",
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "required": ["schema", "version", "run_id", "cost", "time", "iterations", "defects"],
9
+ "properties": {
10
+ "schema": { "const": "kontour.console.economics" },
11
+ "version": { "const": "0.1" },
12
+ "run_id": { "type": "string" },
13
+ "at": { "type": ["string", "null"] },
14
+ "task_slug": { "type": ["string", "null"] },
15
+ "model": { "type": ["string", "null"] },
16
+ "pricing_version": { "type": ["string", "null"] },
17
+ "cost": {
18
+ "type": "object",
19
+ "additionalProperties": true,
20
+ "required": [
21
+ "input_tokens",
22
+ "output_tokens",
23
+ "cache_creation_input_tokens",
24
+ "cache_read_input_tokens",
25
+ "estimated_cost_usd",
26
+ "by_model"
27
+ ],
28
+ "properties": {
29
+ "input_tokens": { "type": "number" },
30
+ "output_tokens": { "type": "number" },
31
+ "cache_creation_input_tokens": { "type": "number" },
32
+ "cache_read_input_tokens": { "type": "number" },
33
+ "estimated_cost_usd": { "type": "number" },
34
+ "by_model": { "type": "array" }
35
+ }
36
+ },
37
+ "time": {
38
+ "type": "object",
39
+ "additionalProperties": true,
40
+ "required": ["wall_clock_s", "human_wait_s"],
41
+ "properties": {
42
+ "wall_clock_s": { "type": "number" },
43
+ "human_wait_s": { "type": "number" }
44
+ }
45
+ },
46
+ "phases": {
47
+ "type": "array",
48
+ "items": {
49
+ "type": "object",
50
+ "additionalProperties": true,
51
+ "required": ["phase"],
52
+ "properties": {
53
+ "phase": { "type": "string" },
54
+ "input_tokens": { "type": "number" },
55
+ "output_tokens": { "type": "number" },
56
+ "cache_creation_input_tokens": { "type": "number" },
57
+ "cache_read_input_tokens": { "type": "number" },
58
+ "estimated_cost_usd": { "type": "number" },
59
+ "wall_clock_s": { "type": "number" }
60
+ }
61
+ }
62
+ },
63
+ "iterations": {
64
+ "type": "object",
65
+ "additionalProperties": true,
66
+ "required": ["count", "route_backs"],
67
+ "properties": {
68
+ "count": { "type": "number" },
69
+ "route_backs": { "type": "number" }
70
+ }
71
+ },
72
+ "defects": {
73
+ "type": "object",
74
+ "additionalProperties": true,
75
+ "required": [
76
+ "gate_fires",
77
+ "findings_by_severity",
78
+ "caught_false_completions",
79
+ "verification_verdict"
80
+ ],
81
+ "properties": {
82
+ "gate_fires": { "type": "number" },
83
+ "findings_by_severity": {
84
+ "type": "object",
85
+ "additionalProperties": true,
86
+ "required": ["critical", "high", "medium", "low"],
87
+ "properties": {
88
+ "critical": { "type": "number" },
89
+ "high": { "type": "number" },
90
+ "medium": { "type": "number" },
91
+ "low": { "type": "number" }
92
+ }
93
+ },
94
+ "caught_false_completions": { "type": "number" },
95
+ "verification_verdict": {
96
+ "type": "string",
97
+ "enum": ["PASS", "FAIL", "NOT_VERIFIED"]
98
+ }
99
+ }
100
+ },
101
+ "delegations": {
102
+ "type": "array",
103
+ "description": "Per-sub-agent delegation facts (#415), one entry per delegated agent_id, joined from <slug>/agents/<agent-id>/events.jsonl (latest delegation/escalation event wins for role/model; outcome derived from the agent's full event set). No per-delegation cost (no runtime isolates per-sub-agent tokens yet — see signals.per_delegation_tokens; cost-per-(role,model) is a console projection). Empty when no --agents-dir or no delegations.",
104
+ "items": {
105
+ "type": "object",
106
+ "additionalProperties": true,
107
+ "required": ["role", "resolved_model"],
108
+ "properties": {
109
+ "agent_id": { "type": ["string", "null"] },
110
+ "role": { "type": "string" },
111
+ "resolved_model": { "type": "string" },
112
+ "summary": { "type": ["string", "null"] },
113
+ "escalated_from": { "type": "string" },
114
+ "dispatch_count": {
115
+ "type": "number",
116
+ "description": "How many times the orchestrator (re)dispatched this agent_id (delegation + escalation events). >1 means it was re-prompted — an orchestrator-observable rework signal that needs no sub-agent introspection."
117
+ },
118
+ "outcome": {
119
+ "type": "string",
120
+ "description": "Derived ONLY from real recorded signals, never fabricated. 'unavailable' = no terminal verdict recorded on this harness, distinct from a real accept.",
121
+ "enum": ["accepted", "rework", "diverged", "failed", "unavailable"]
122
+ }
123
+ }
124
+ }
125
+ },
126
+ "signals": {
127
+ "type": "object",
128
+ "additionalProperties": true,
129
+ "description": "What telemetry the current runtime actually exposed (harness-capability declaration — see docs/specs/harness-capability-matrix.md). Lets a consumer distinguish a real zero from a harness-blind gap.",
130
+ "properties": {
131
+ "runtime": { "type": ["string", "null"] },
132
+ "per_delegation_tokens": {
133
+ "type": "boolean",
134
+ "description": "Whether the runtime isolates per-sub-agent token usage. False on every runtime today → per-delegation cost is unavailable."
135
+ },
136
+ "per_delegation_outcome": {
137
+ "type": "string",
138
+ "enum": ["full", "partial", "none", "n/a"],
139
+ "description": "Coverage of the per-delegation outcome signal on this run: full=every delegation has a real outcome; partial=some; none=delegations exist but none had a verdict/escalation; n/a=no delegations observed."
140
+ }
141
+ }
142
+ },
143
+ "tenant_id": { "type": ["string", "null"] }
144
+ }
145
+ }