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