@kontourai/flow-agents 3.8.0 → 3.10.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 (109) hide show
  1. package/.github/workflows/ci.yml +8 -0
  2. package/CHANGELOG.md +24 -0
  3. package/README.md +1 -1
  4. package/build/src/builder-flow-runtime.js +16 -10
  5. package/build/src/cli/assignment-provider.js +4 -18
  6. package/build/src/cli/continuation-adapter.d.ts +26 -0
  7. package/build/src/cli/continuation-adapter.js +243 -0
  8. package/build/src/cli/validate-workflow-artifacts.js +21 -1
  9. package/build/src/cli/workflow-sidecar.js +15 -20
  10. package/build/src/cli/workflow.js +99 -6
  11. package/build/src/continuation-driver.d.ts +116 -0
  12. package/build/src/continuation-driver.js +527 -0
  13. package/build/src/index.d.ts +2 -0
  14. package/build/src/index.js +1 -0
  15. package/build/src/tools/build-universal-bundles.js +52 -25
  16. package/build/src/tools/validate-source-tree.js +5 -2
  17. package/context/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  18. package/context/scripts/hooks/stop-goal-fit.js +244 -73
  19. package/context/scripts/hooks/workflow-steering.js +24 -15
  20. package/context/scripts/telemetry/lib/pricing.sh +18 -6
  21. package/context/scripts/telemetry/lib/transport.sh +139 -37
  22. package/context/scripts/telemetry/lib/usage.sh +282 -32
  23. package/context/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  24. package/context/scripts/telemetry/telemetry.sh +76 -1
  25. package/docs/coordination-guide.md +21 -5
  26. package/docs/fixture-ownership.md +2 -1
  27. package/docs/public-workflow-cli.md +98 -0
  28. package/docs/spec/runtime-hook-surface.md +8 -4
  29. package/docs/workflow-usage-guide.md +7 -0
  30. package/evals/acceptance/prove-capture-teeth-declared.sh +13 -0
  31. package/evals/acceptance/prove-capture-teeth.sh +13 -0
  32. package/evals/ci/run-baseline.sh +2 -0
  33. package/evals/fixtures/codex-legacy-agents/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md +25 -0
  34. package/evals/fixtures/telemetry/usage-transcript-adversarial.jsonl +2 -0
  35. package/evals/fixtures/telemetry/usage-transcript-malformed-sandwich.jsonl +3 -0
  36. package/evals/fixtures/telemetry/usage-transcript-negative-tokens.jsonl +1 -0
  37. package/evals/fixtures/telemetry/usage-transcript-oversized-prefix.jsonl +201 -0
  38. package/evals/fixtures/telemetry/usage-transcript-oversized-tokens.jsonl +1 -0
  39. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-lower.jsonl +1 -0
  40. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-upper.jsonl +1 -0
  41. package/evals/fixtures/telemetry/usage-transcript-secret-model-jwt.jsonl +1 -0
  42. package/evals/fixtures/telemetry/usage-transcript-secret-model.jsonl +1 -0
  43. package/evals/fixtures/telemetry/usage-transcript-torn-trailing.jsonl +2 -0
  44. package/evals/integration/test_actor_identity.sh +63 -0
  45. package/evals/integration/test_assignment_provider_local_file.sh +34 -0
  46. package/evals/integration/test_builder_entry_enforcement.sh +5 -1
  47. package/evals/integration/test_bundle_install.sh +106 -17
  48. package/evals/integration/test_bundle_lifecycle.sh +17 -3
  49. package/evals/integration/test_captured_fail_reconciliation.sh +11 -0
  50. package/evals/integration/test_ci_actor_identity.sh +2 -2
  51. package/evals/integration/test_command_log_concurrency.sh +11 -0
  52. package/evals/integration/test_command_log_fork_classification.sh +11 -0
  53. package/evals/integration/test_command_log_integrity.sh +11 -0
  54. package/evals/integration/test_current_json_per_actor.sh +416 -21
  55. package/evals/integration/test_enforcer_expects_driven.sh +11 -0
  56. package/evals/integration/test_ensure_session_ownership_guard.sh +39 -0
  57. package/evals/integration/test_evidence_capture_hook.sh +25 -6
  58. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  59. package/evals/integration/test_flowdef_union_floor_regression.sh +11 -0
  60. package/evals/integration/test_gate_bypass_chain.sh +11 -0
  61. package/evals/integration/test_gate_lockdown.sh +10 -0
  62. package/evals/integration/test_goal_fit_ghost_session.sh +11 -0
  63. package/evals/integration/test_goal_fit_hook.sh +257 -9
  64. package/evals/integration/test_goal_fit_rederive.sh +11 -0
  65. package/evals/integration/test_install_merge.sh +141 -3
  66. package/evals/integration/test_liveness_conflict_injection.sh +39 -3
  67. package/evals/integration/test_liveness_heartbeat.sh +46 -9
  68. package/evals/integration/test_phase_map_and_gate_claim.sh +10 -1
  69. package/evals/integration/test_public_workflow_cli.sh +6 -6
  70. package/evals/integration/test_session_resume_roundtrip.sh +30 -0
  71. package/evals/integration/test_stop_hook_release.sh +111 -11
  72. package/evals/integration/test_telemetry_tool_usage.sh +394 -0
  73. package/evals/integration/test_verify_hold.sh +64 -1
  74. package/evals/integration/test_workflow_sidecar_writer.sh +10 -1
  75. package/evals/integration/test_workflow_steering_hook.sh +20 -4
  76. package/evals/run.sh +2 -0
  77. package/evals/static/test_universal_bundles.sh +123 -0
  78. package/package.json +4 -4
  79. package/packaging/README.md +21 -0
  80. package/packaging/codex-legacy-agents-fingerprints.json +33 -0
  81. package/scripts/README.md +2 -1
  82. package/scripts/audit-codex-legacy-agents.js +43 -0
  83. package/scripts/classify-codex-legacy-agents.js +71 -0
  84. package/scripts/hooks/evidence-capture.js +18 -6
  85. package/scripts/hooks/lib/actor-identity.js +62 -29
  86. package/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  87. package/scripts/hooks/lib/current-pointer.js +114 -12
  88. package/scripts/hooks/lib/liveness-heartbeat.js +15 -12
  89. package/scripts/hooks/stop-goal-fit.js +244 -73
  90. package/scripts/hooks/workflow-steering.js +24 -15
  91. package/scripts/install-codex-home.sh +8 -7
  92. package/scripts/telemetry/lib/pricing.sh +18 -6
  93. package/scripts/telemetry/lib/transport.sh +71 -0
  94. package/scripts/telemetry/lib/usage.sh +282 -32
  95. package/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  96. package/scripts/telemetry/telemetry.sh +76 -1
  97. package/src/builder-flow-runtime.ts +9 -3
  98. package/src/cli/assignment-provider.ts +5 -26
  99. package/src/cli/builder-flow-runtime.test.mjs +215 -2
  100. package/src/cli/continuation-adapter.ts +265 -0
  101. package/src/cli/continuation-driver.test.mjs +1303 -0
  102. package/src/cli/public-api.test.mjs +1 -0
  103. package/src/cli/validate-workflow-artifacts.ts +19 -1
  104. package/src/cli/workflow-sidecar.ts +18 -23
  105. package/src/cli/workflow.ts +113 -6
  106. package/src/continuation-driver.ts +638 -0
  107. package/src/index.ts +24 -0
  108. package/src/tools/build-universal-bundles.ts +61 -25
  109. package/src/tools/validate-source-tree.ts +5 -2
@@ -6,6 +6,7 @@ import { isDeepStrictEqual } from "node:util";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { validateDefinition } from "@kontourai/flow";
8
8
  import { loadBuilderFlowRun } from "../builder-flow-run-adapter.js";
9
+ import { driveBuilderFlowSession, withContinuationDriverLock } from "../continuation-driver.js";
9
10
  import { inspectBuilderFlowSession, recoverBuilderFlowSession, syncBuilderFlowSession } from "../builder-flow-runtime.js";
10
11
  import { flowAgentsPackageRoot, flowAgentsPackageVersion } from "../lib/package-version.js";
11
12
  import { pinnedFlowAgentsCommand } from "../lib/pinned-cli-command.js";
@@ -14,12 +15,13 @@ import { flagBool, flagList, flagString, parseArgs } from "../lib/args.js";
14
15
  import { main as builderRun } from "./builder-run.js";
15
16
  import { currentWorkflowSessionDir, isMeaningfulTestCommand, mainFromPublicWorkflow, WORKFLOW_WRITER_CONTRACT_VERSION } from "./workflow-sidecar.js";
16
17
  import { resolveCurrentAssignmentActor, withSubjectLock } from "./assignment-provider.js";
18
+ import { assertLoadedContinuationAdapterIntegrity, executeLoadedContinuationAdapter, loadContinuationAdapterCommand, waitForContinuationBarrier } from "./continuation-adapter.js";
17
19
  export const WORKFLOW_CONTRACT_VERSION = "1.0";
18
20
  const PACKAGE_ROOT = flowAgentsPackageRoot();
19
21
  const REQUIRE = createRequire(import.meta.url);
20
22
  const PACKAGE_METADATA = readJsonFile(path.join(PACKAGE_ROOT, "package.json"), "Flow Agents package metadata");
21
23
  const CLI_VERSION = flowAgentsPackageVersion();
22
- const PUBLIC_VERBS = ["start", "status", "evidence", "critique", "pause", "resume", "release", "cancel", "archive", "doctor"];
24
+ const PUBLIC_VERBS = ["start", "status", "evidence", "critique", "drive", "pause", "resume", "release", "cancel", "archive", "doctor"];
23
25
  function usage() {
24
26
  console.log(`Usage: flow-agents workflow <verb> [options]
25
27
 
@@ -28,6 +30,7 @@ Public workflow verbs:
28
30
  status Show the current canonical run and projected next action.
29
31
  evidence Record evidence for the current Flow gate and synchronize it.
30
32
  critique Record review critique directly into the current trust bundle.
33
+ drive Continue the canonical run through an explicit runtime adapter.
31
34
  pause Pause the current run as its assignment actor.
32
35
  resume Resume the current paused run as its assignment actor.
33
36
  release Release the current assignment without canceling the run.
@@ -60,11 +63,69 @@ export async function main(argv) {
60
63
  return evidence(sessionDir, argv.slice(1), flagBool(parsed.flags, "json"));
61
64
  if (verb === "critique")
62
65
  return critique(sessionDir, argv.slice(1), flagBool(parsed.flags, "json"));
66
+ if (verb === "drive")
67
+ return drive(sessionDir, argv.slice(1), flagBool(parsed.flags, "json"));
63
68
  const forwarded = stripPublicFlags(argv.slice(1), new Set(["artifact-root", "session-dir", "json"]));
64
69
  if (verb === "release" && !flagString(parsed.flags, "reason"))
65
70
  throw new Error("workflow release requires --reason <text>");
66
71
  return builderRun([verb === "release" ? "release-assignment" : verb, "--session-dir", sessionDir, ...forwarded]);
67
72
  }
73
+ async function drive(sessionDir, argv, json) {
74
+ const parsed = parseArgs(argv);
75
+ assertOnlyFlags(parsed.flags, new Set(["artifact-root", "session-dir", "json", "adapter-command-file", "max-turns", "turn-timeout-ms", "barrier-wait-ms", "barrier-poll-ms"]), "workflow drive");
76
+ const adapterCommandFile = flagString(parsed.flags, "adapter-command-file");
77
+ if (!adapterCommandFile)
78
+ throw new Error("workflow drive requires --adapter-command-file <path>");
79
+ const maxTurns = integerFlag(parsed.flags, "max-turns", 4, 1, 100);
80
+ const turnTimeoutMs = integerFlag(parsed.flags, "turn-timeout-ms", 900_000, 1, 86_400_000);
81
+ const barrierWaitMs = integerFlag(parsed.flags, "barrier-wait-ms", 300_000, 0, 86_400_000);
82
+ const barrierPollMs = integerFlag(parsed.flags, "barrier-poll-ms", 1_000, 1, 60_000);
83
+ const { slug, projectRoot } = readBoundSession(sessionDir);
84
+ assertOrdinaryMatchingAssignmentActor(sessionDir, slug);
85
+ const adapterCommand = loadContinuationAdapterCommand(adapterCommandFile);
86
+ const result = await withContinuationDriverLock(sessionDir, async (lock) => {
87
+ assertOrdinaryMatchingAssignmentActor(sessionDir, slug);
88
+ return driveBuilderFlowSession({
89
+ sessionDir,
90
+ maxTurns,
91
+ adapterCommandIdentity: adapterCommand.identity,
92
+ authorizeTurn: async () => { assertOrdinaryMatchingAssignmentActor(sessionDir, slug); },
93
+ issueTurnAuthority: async (request) => {
94
+ const currentAssignment = assertOrdinaryMatchingAssignmentActor(sessionDir, slug);
95
+ assertLoadedContinuationAdapterIntegrity(adapterCommand);
96
+ const authority = loadContinuationTurnAuthority();
97
+ return authority.issueActiveTurnAuthority({
98
+ sessionDir,
99
+ runId: request.run_id,
100
+ definitionId: request.definition_id,
101
+ currentStep: request.current_step,
102
+ iteration: request.iteration,
103
+ maxTurns: request.max_turns,
104
+ adapterCommandIdentity: adapterCommand.identity,
105
+ assignmentActor: currentAssignment.actorKey,
106
+ assignmentActorStruct: currentAssignment.actor,
107
+ lock,
108
+ timeoutMs: turnTimeoutMs,
109
+ });
110
+ },
111
+ execute: async (request, context) => executeLoadedContinuationAdapter(adapterCommand, request, {
112
+ cwd: projectRoot,
113
+ timeoutMs: turnTimeoutMs,
114
+ continuationTurnSecret: context?.continuationTurnSecret,
115
+ continuationRunId: context?.continuationRunId,
116
+ }),
117
+ waitForBarrier: async (barrier) => waitForContinuationBarrier(barrier, { maxWaitMs: barrierWaitMs, pollMs: barrierPollMs }),
118
+ });
119
+ });
120
+ if (json)
121
+ console.log(JSON.stringify(result));
122
+ else
123
+ console.log(`Continuation driver ${result.outcome} after ${result.turns_started} turn(s); canonical Flow is ${result.snapshot.status} at ${result.snapshot.current_step}.`);
124
+ return 0;
125
+ }
126
+ function loadContinuationTurnAuthority() {
127
+ return REQUIRE(path.resolve(PACKAGE_ROOT, "scripts", "hooks", "lib", "continuation-turn-authority.js"));
128
+ }
68
129
  async function start(argv) {
69
130
  const parsed = parseArgs(argv);
70
131
  assertOnlyFlags(parsed.flags, new Set(["flow", "work-item", "task-slug", "artifact-root", "source-request", "summary", "title", "criterion", "assignment-provider", "effective-state-json"]), "workflow start");
@@ -518,14 +579,35 @@ function readAssignment(sessionDir, slug) {
518
579
  return readJsonFile(file, "workflow assignment");
519
580
  }
520
581
  function assertMatchingAssignmentActor(sessionDir, slug) {
521
- const assignment = readActiveAssignment(sessionDir, slug);
522
- const caller = resolveCurrentAssignmentActor();
523
- const normalizeActor = (value) => value && typeof value === "object" ? { ...value, human: value.human ?? null } : null;
524
- if (assignment.actor_key !== caller.actorKey || !isDeepStrictEqual(normalizeActor(assignment.actor), normalizeActor(caller.actor))) {
525
- throw new Error("workflow mutation requires the session's active, matching assignment actor");
582
+ const { assignment, caller, matches } = assignmentActorContext(sessionDir, slug);
583
+ if (matches)
584
+ return caller;
585
+ const authority = loadContinuationTurnAuthority().validateSignedActiveTurnAssignmentAuthority({
586
+ sessionDir,
587
+ runId: process.env.FLOW_AGENTS_CONTINUATION_RUN_ID,
588
+ turnSecret: process.env.FLOW_AGENTS_CONTINUATION_TURN_SECRET,
589
+ });
590
+ if (authority.valid && authority.record
591
+ && assignment.actor_key === authority.record.assignment_actor
592
+ && isDeepStrictEqual(normalizeAssignmentActor(assignment.actor), normalizeAssignmentActor(authority.record.assignment_actor_struct))) {
593
+ return { actorKey: authority.record.assignment_actor, actor: normalizeAssignmentActor(authority.record.assignment_actor_struct) };
526
594
  }
595
+ throw new Error("workflow mutation requires the session's active, matching assignment actor");
596
+ }
597
+ function assertOrdinaryMatchingAssignmentActor(sessionDir, slug) {
598
+ const { caller, matches } = assignmentActorContext(sessionDir, slug);
599
+ if (!matches)
600
+ throw new Error("workflow mutation requires the session's active, matching assignment actor");
527
601
  return caller;
528
602
  }
603
+ function assignmentActorContext(sessionDir, slug) {
604
+ const assignment = readActiveAssignment(sessionDir, slug);
605
+ const caller = resolveCurrentAssignmentActor();
606
+ return { assignment, caller, matches: assignment.actor_key === caller.actorKey && isDeepStrictEqual(normalizeAssignmentActor(assignment.actor), normalizeAssignmentActor(caller.actor)) };
607
+ }
608
+ function normalizeAssignmentActor(value) {
609
+ return value && typeof value === "object" ? { ...value, human: value.human ?? null } : null;
610
+ }
529
611
  function readActiveAssignment(sessionDir, slug) {
530
612
  const assignment = readAssignment(sessionDir, slug);
531
613
  if (assignment.status !== "claimed" || assignment.artifact_dir !== slug || typeof assignment.actor_key !== "string" || !assignment.actor_key || !assignment.actor || typeof assignment.actor !== "object" || Array.isArray(assignment.actor)) {
@@ -610,6 +692,17 @@ function assertOnlyFlags(flags, allowed, command) {
610
692
  if (unsupported)
611
693
  throw new Error(`${command} does not support --${unsupported}`);
612
694
  }
695
+ function integerFlag(flags, name, fallback, min, max) {
696
+ const raw = flagString(flags, name);
697
+ if (raw === undefined)
698
+ return fallback;
699
+ if (!/^(0|[1-9][0-9]*)$/.test(raw))
700
+ throw new Error(`workflow drive --${name} must be an integer from ${min} through ${max}`);
701
+ const value = Number(raw);
702
+ if (!Number.isSafeInteger(value) || value < min || value > max)
703
+ throw new Error(`workflow drive --${name} must be an integer from ${min} through ${max}`);
704
+ return value;
705
+ }
613
706
  function isWithin(candidate, root) {
614
707
  const relative = path.relative(root, candidate);
615
708
  return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
@@ -0,0 +1,116 @@
1
+ export type ContinuationBarrier = {
2
+ kind: "pid";
3
+ pid: number;
4
+ } | {
5
+ kind: "deadline";
6
+ at: string;
7
+ };
8
+ export type ContinuationSnapshot = {
9
+ run_id: string;
10
+ definition_id: string;
11
+ status: string;
12
+ disposition: "continue" | "waiting" | "done" | "failed";
13
+ current_step: string;
14
+ next_action: Record<string, unknown> | null;
15
+ };
16
+ export type ContinuationTurnRequest = {
17
+ schema_version: "1.0";
18
+ run_id: string;
19
+ definition_id: string;
20
+ current_step: string;
21
+ iteration: number;
22
+ max_turns: number;
23
+ next_action: Record<string, unknown> | null;
24
+ };
25
+ export type ContinuationTurnResult = {
26
+ status: "completed";
27
+ summary?: string;
28
+ } | {
29
+ status: "wait";
30
+ barrier: ContinuationBarrier;
31
+ summary?: string;
32
+ };
33
+ export declare class ContinuationAdapterTimeoutError extends Error {
34
+ readonly code = "CONTINUATION_ADAPTER_TIMEOUT";
35
+ constructor(timeoutMs: number);
36
+ }
37
+ export type ContinuationDriverState = {
38
+ schema_version: "1.0";
39
+ run_id: string;
40
+ definition_id: string;
41
+ max_turns: number;
42
+ adapter_command_identity: string | null;
43
+ status: "active" | "waiting" | "done" | "failed" | "budget_exhausted";
44
+ turns_started: number;
45
+ active_turn_step?: string | null;
46
+ active_turn_public_key_digest?: string | null;
47
+ pending_barrier: ContinuationBarrier | null;
48
+ updated_at: string;
49
+ };
50
+ export type ContinuationDriverEvent = {
51
+ schema_version: "1.0";
52
+ type: "started" | "turn_started" | "turn_completed" | "gate_not_advanced" | "turn_failed" | "authority_cleanup_failed" | "parked" | "resumed" | "done" | "budget_exhausted";
53
+ run_id: string;
54
+ definition_id: string;
55
+ current_step: string;
56
+ turns_started: number;
57
+ at: string;
58
+ barrier?: ContinuationBarrier;
59
+ summary?: string;
60
+ failure_kind?: "adapter_error" | "timeout";
61
+ };
62
+ export interface ContinuationStateStore {
63
+ load(): ContinuationDriverState | null;
64
+ save(state: ContinuationDriverState): void;
65
+ append(event: ContinuationDriverEvent): void;
66
+ }
67
+ export interface ContinuationRuntimePort {
68
+ inspect(): Promise<ContinuationSnapshot>;
69
+ synchronize(): Promise<ContinuationSnapshot>;
70
+ execute(request: ContinuationTurnRequest, context?: ContinuationTurnContext): Promise<ContinuationTurnResult>;
71
+ }
72
+ export type ContinuationTurnContext = {
73
+ continuationTurnSecret?: string;
74
+ continuationRunId?: string;
75
+ };
76
+ export type ContinuationDriverLockLease = {
77
+ pid: number;
78
+ token: string;
79
+ created_at: string;
80
+ };
81
+ export type ContinuationTurnAuthority = {
82
+ runId: string;
83
+ turnSecret: string;
84
+ publicKeyDigest: string;
85
+ cleanup(): boolean;
86
+ };
87
+ export type ContinuationDriverOutcome = {
88
+ outcome: "done" | "waiting" | "failed" | "budget_exhausted";
89
+ turns_started: number;
90
+ snapshot: ContinuationSnapshot;
91
+ barrier?: ContinuationBarrier;
92
+ };
93
+ export interface RunContinuationDriverInput {
94
+ maxTurns: number;
95
+ adapterCommandIdentity?: string;
96
+ runtime: ContinuationRuntimePort;
97
+ store: ContinuationStateStore;
98
+ waitForBarrier?: (barrier: ContinuationBarrier) => Promise<"ready" | "pending">;
99
+ authorizeTurn?: () => Promise<void>;
100
+ issueTurnAuthority?: (request: ContinuationTurnRequest) => Promise<ContinuationTurnAuthority>;
101
+ now?: () => Date;
102
+ }
103
+ export interface DriveBuilderFlowSessionInput {
104
+ sessionDir: string;
105
+ maxTurns: number;
106
+ adapterCommandIdentity?: string;
107
+ execute: ContinuationRuntimePort["execute"];
108
+ waitForBarrier?: RunContinuationDriverInput["waitForBarrier"];
109
+ authorizeTurn?: RunContinuationDriverInput["authorizeTurn"];
110
+ issueTurnAuthority?: RunContinuationDriverInput["issueTurnAuthority"];
111
+ now?: () => Date;
112
+ }
113
+ export declare function runContinuationDriver(input: RunContinuationDriverInput): Promise<ContinuationDriverOutcome>;
114
+ export declare function driveBuilderFlowSession(input: DriveBuilderFlowSessionInput): Promise<ContinuationDriverOutcome>;
115
+ export declare function withContinuationDriverLock<T>(sessionDirInput: string, body: (lease: ContinuationDriverLockLease) => Promise<T>): Promise<T>;
116
+ export declare function createFileContinuationStore(sessionDirInput: string): ContinuationStateStore;