@kontourai/flow-agents 3.9.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 (106) hide show
  1. package/.github/workflows/ci.yml +8 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +1 -1
  4. package/build/src/cli/assignment-provider.js +4 -18
  5. package/build/src/cli/continuation-adapter.d.ts +10 -8
  6. package/build/src/cli/continuation-adapter.js +22 -4
  7. package/build/src/cli/validate-workflow-artifacts.js +21 -1
  8. package/build/src/cli/workflow-sidecar.js +15 -20
  9. package/build/src/cli/workflow.js +58 -11
  10. package/build/src/continuation-driver.d.ts +27 -3
  11. package/build/src/continuation-driver.js +97 -14
  12. package/build/src/index.d.ts +2 -2
  13. package/build/src/index.js +1 -1
  14. package/build/src/tools/build-universal-bundles.js +52 -25
  15. package/build/src/tools/validate-source-tree.js +5 -2
  16. package/context/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  17. package/context/scripts/hooks/stop-goal-fit.js +244 -73
  18. package/context/scripts/hooks/workflow-steering.js +24 -15
  19. package/context/scripts/telemetry/lib/pricing.sh +18 -6
  20. package/context/scripts/telemetry/lib/transport.sh +139 -37
  21. package/context/scripts/telemetry/lib/usage.sh +282 -32
  22. package/context/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  23. package/context/scripts/telemetry/telemetry.sh +76 -1
  24. package/docs/coordination-guide.md +21 -5
  25. package/docs/fixture-ownership.md +2 -1
  26. package/docs/public-workflow-cli.md +44 -0
  27. package/docs/spec/runtime-hook-surface.md +8 -4
  28. package/evals/acceptance/prove-capture-teeth-declared.sh +13 -0
  29. package/evals/acceptance/prove-capture-teeth.sh +13 -0
  30. package/evals/ci/run-baseline.sh +2 -0
  31. package/evals/fixtures/codex-legacy-agents/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md +25 -0
  32. package/evals/fixtures/telemetry/usage-transcript-adversarial.jsonl +2 -0
  33. package/evals/fixtures/telemetry/usage-transcript-malformed-sandwich.jsonl +3 -0
  34. package/evals/fixtures/telemetry/usage-transcript-negative-tokens.jsonl +1 -0
  35. package/evals/fixtures/telemetry/usage-transcript-oversized-prefix.jsonl +201 -0
  36. package/evals/fixtures/telemetry/usage-transcript-oversized-tokens.jsonl +1 -0
  37. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-lower.jsonl +1 -0
  38. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-upper.jsonl +1 -0
  39. package/evals/fixtures/telemetry/usage-transcript-secret-model-jwt.jsonl +1 -0
  40. package/evals/fixtures/telemetry/usage-transcript-secret-model.jsonl +1 -0
  41. package/evals/fixtures/telemetry/usage-transcript-torn-trailing.jsonl +2 -0
  42. package/evals/integration/test_actor_identity.sh +63 -0
  43. package/evals/integration/test_assignment_provider_local_file.sh +34 -0
  44. package/evals/integration/test_builder_entry_enforcement.sh +5 -1
  45. package/evals/integration/test_bundle_install.sh +106 -17
  46. package/evals/integration/test_bundle_lifecycle.sh +17 -3
  47. package/evals/integration/test_captured_fail_reconciliation.sh +11 -0
  48. package/evals/integration/test_ci_actor_identity.sh +2 -2
  49. package/evals/integration/test_command_log_concurrency.sh +11 -0
  50. package/evals/integration/test_command_log_fork_classification.sh +11 -0
  51. package/evals/integration/test_command_log_integrity.sh +11 -0
  52. package/evals/integration/test_current_json_per_actor.sh +416 -21
  53. package/evals/integration/test_enforcer_expects_driven.sh +11 -0
  54. package/evals/integration/test_ensure_session_ownership_guard.sh +39 -0
  55. package/evals/integration/test_evidence_capture_hook.sh +25 -6
  56. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  57. package/evals/integration/test_flowdef_union_floor_regression.sh +11 -0
  58. package/evals/integration/test_gate_bypass_chain.sh +11 -0
  59. package/evals/integration/test_gate_lockdown.sh +10 -0
  60. package/evals/integration/test_goal_fit_ghost_session.sh +11 -0
  61. package/evals/integration/test_goal_fit_hook.sh +257 -9
  62. package/evals/integration/test_goal_fit_rederive.sh +11 -0
  63. package/evals/integration/test_install_merge.sh +141 -3
  64. package/evals/integration/test_liveness_conflict_injection.sh +39 -3
  65. package/evals/integration/test_liveness_heartbeat.sh +46 -9
  66. package/evals/integration/test_phase_map_and_gate_claim.sh +10 -1
  67. package/evals/integration/test_public_workflow_cli.sh +6 -6
  68. package/evals/integration/test_session_resume_roundtrip.sh +30 -0
  69. package/evals/integration/test_stop_hook_release.sh +111 -11
  70. package/evals/integration/test_telemetry_tool_usage.sh +394 -0
  71. package/evals/integration/test_verify_hold.sh +64 -1
  72. package/evals/integration/test_workflow_sidecar_writer.sh +10 -1
  73. package/evals/integration/test_workflow_steering_hook.sh +20 -4
  74. package/evals/run.sh +2 -0
  75. package/evals/static/test_universal_bundles.sh +123 -0
  76. package/package.json +1 -1
  77. package/packaging/README.md +21 -0
  78. package/packaging/codex-legacy-agents-fingerprints.json +33 -0
  79. package/scripts/README.md +2 -1
  80. package/scripts/audit-codex-legacy-agents.js +43 -0
  81. package/scripts/classify-codex-legacy-agents.js +71 -0
  82. package/scripts/hooks/evidence-capture.js +18 -6
  83. package/scripts/hooks/lib/actor-identity.js +62 -29
  84. package/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  85. package/scripts/hooks/lib/current-pointer.js +114 -12
  86. package/scripts/hooks/lib/liveness-heartbeat.js +15 -12
  87. package/scripts/hooks/stop-goal-fit.js +244 -73
  88. package/scripts/hooks/workflow-steering.js +24 -15
  89. package/scripts/install-codex-home.sh +8 -7
  90. package/scripts/telemetry/lib/pricing.sh +18 -6
  91. package/scripts/telemetry/lib/transport.sh +71 -0
  92. package/scripts/telemetry/lib/usage.sh +282 -32
  93. package/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  94. package/scripts/telemetry/telemetry.sh +76 -1
  95. package/src/cli/assignment-provider.ts +5 -26
  96. package/src/cli/builder-flow-runtime.test.mjs +93 -6
  97. package/src/cli/continuation-adapter.ts +33 -7
  98. package/src/cli/continuation-driver.test.mjs +741 -2
  99. package/src/cli/public-api.test.mjs +1 -0
  100. package/src/cli/validate-workflow-artifacts.ts +19 -1
  101. package/src/cli/workflow-sidecar.ts +18 -23
  102. package/src/cli/workflow.ts +71 -11
  103. package/src/continuation-driver.ts +125 -19
  104. package/src/index.ts +4 -0
  105. package/src/tools/build-universal-bundles.ts +61 -25
  106. package/src/tools/validate-source-tree.ts +5 -2
@@ -4,11 +4,19 @@ import { randomUUID } from "node:crypto";
4
4
  import * as path from "node:path";
5
5
  import { inspectBuilderFlowSession, syncBuilderFlowSession } from "./builder-flow-runtime.js";
6
6
  import { atomicWriteJson, ensureSafeDirectory } from "./lib/fs.js";
7
+ export class ContinuationAdapterTimeoutError extends Error {
8
+ code = "CONTINUATION_ADAPTER_TIMEOUT";
9
+ constructor(timeoutMs) {
10
+ super(`continuation adapter timed out after ${timeoutMs}ms`);
11
+ this.name = "ContinuationAdapterTimeoutError";
12
+ }
13
+ }
7
14
  export async function runContinuationDriver(input) {
8
15
  assertMaxTurns(input.maxTurns);
9
16
  const now = input.now ?? (() => new Date());
10
17
  const waitForBarrier = input.waitForBarrier ?? (async () => "pending");
11
18
  const authorizeTurn = input.authorizeTurn ?? (async () => { });
19
+ const issueTurnAuthority = input.issueTurnAuthority;
12
20
  const adapterCommandIdentity = input.adapterCommandIdentity ?? null;
13
21
  let snapshot = validateSnapshot(await input.runtime.inspect());
14
22
  let state = loadOrCreateState(input.store, snapshot, input.maxTurns, adapterCommandIdentity, now);
@@ -25,10 +33,10 @@ export async function runContinuationDriver(input) {
25
33
  const barrier = state.pending_barrier;
26
34
  const readiness = await waitForBarrier(barrier);
27
35
  if (readiness === "pending") {
28
- state = saveState(input.store, state, { status: "waiting" }, now);
36
+ state = saveState(input.store, state, { status: "waiting", active_turn_step: null, active_turn_public_key_digest: null }, now);
29
37
  return { outcome: "waiting", turns_started: state.turns_started, snapshot, barrier };
30
38
  }
31
- state = saveState(input.store, state, { status: "active", pending_barrier: null }, now);
39
+ state = saveState(input.store, state, { status: "active", active_turn_step: null, active_turn_public_key_digest: null, pending_barrier: null }, now);
32
40
  appendEvent(input.store, state, snapshot, "resumed", now, { barrier });
33
41
  }
34
42
  snapshot = validateSnapshot(await input.runtime.synchronize());
@@ -39,17 +47,25 @@ export async function runContinuationDriver(input) {
39
47
  if (initialOutcome === "failed")
40
48
  return finishFailed(input.store, state, snapshot, now);
41
49
  if (initialOutcome === "waiting") {
42
- state = saveState(input.store, state, { status: "waiting" }, now);
50
+ state = saveState(input.store, state, { status: "waiting", active_turn_step: null, active_turn_public_key_digest: null }, now);
43
51
  return { outcome: "waiting", turns_started: state.turns_started, snapshot };
44
52
  }
45
53
  while (state.turns_started < input.maxTurns) {
46
54
  await authorizeTurn();
47
55
  const iteration = state.turns_started + 1;
48
- state = saveState(input.store, state, { status: "active", turns_started: iteration }, now);
56
+ state = saveState(input.store, state, {
57
+ status: "active",
58
+ turns_started: iteration,
59
+ active_turn_step: snapshot.current_step,
60
+ active_turn_public_key_digest: null,
61
+ }, now);
49
62
  appendEvent(input.store, state, snapshot, "turn_started", now);
50
63
  let result;
64
+ let request;
65
+ let authority;
66
+ let terminalized = false;
51
67
  try {
52
- result = validateTurnResult(await input.runtime.execute(Object.freeze({
68
+ request = Object.freeze({
53
69
  schema_version: "1.0",
54
70
  run_id: snapshot.run_id,
55
71
  definition_id: snapshot.definition_id,
@@ -57,23 +73,61 @@ export async function runContinuationDriver(input) {
57
73
  iteration,
58
74
  max_turns: input.maxTurns,
59
75
  next_action: snapshot.next_action ? structuredClone(snapshot.next_action) : null,
60
- })));
76
+ });
77
+ authority = issueTurnAuthority ? await issueTurnAuthority(request) : undefined;
78
+ if (authority) {
79
+ state = saveState(input.store, state, { active_turn_public_key_digest: authority.publicKeyDigest }, now);
80
+ }
81
+ result = validateTurnResult(await input.runtime.execute(request, authority ? {
82
+ continuationTurnSecret: authority.turnSecret,
83
+ continuationRunId: authority.runId,
84
+ } : undefined));
61
85
  }
62
86
  catch (error) {
63
- appendEvent(input.store, state, snapshot, "turn_failed", now, { summary: boundedErrorMessage(error) });
87
+ const summary = boundedErrorMessage(error);
88
+ appendEvent(input.store, state, snapshot, "turn_failed", now, { summary, failure_kind: error instanceof ContinuationAdapterTimeoutError ? "timeout" : "adapter_error" });
64
89
  snapshot = validateSnapshot(await input.runtime.synchronize());
65
90
  assertMissionIdentity(state, snapshot);
66
91
  const outcome = canonicalOutcome(snapshot);
67
- if (outcome === "done")
92
+ if (outcome === "done") {
93
+ terminalized = true;
68
94
  return finishDone(input.store, state, snapshot, now);
69
- if (outcome === "failed")
95
+ }
96
+ if (outcome === "failed") {
97
+ terminalized = true;
70
98
  return finishFailed(input.store, state, snapshot, now);
99
+ }
71
100
  if (outcome === "waiting") {
72
101
  state = saveState(input.store, state, { status: "waiting" }, now);
73
102
  return { outcome: "waiting", turns_started: state.turns_started, snapshot };
74
103
  }
75
104
  continue;
76
105
  }
106
+ finally {
107
+ let cleanupFailure;
108
+ try {
109
+ if (authority && !authority.cleanup())
110
+ cleanupFailure = "continuation turn authority cleanup returned false";
111
+ }
112
+ catch (error) {
113
+ cleanupFailure = boundedErrorMessage(error);
114
+ }
115
+ finally {
116
+ if (!terminalized)
117
+ state = saveState(input.store, state, {
118
+ active_turn_step: null,
119
+ active_turn_public_key_digest: null,
120
+ }, now);
121
+ if (cleanupFailure) {
122
+ try {
123
+ appendEvent(input.store, state, snapshot, "authority_cleanup_failed", now, { summary: cleanupFailure });
124
+ }
125
+ catch {
126
+ // Audit is best effort. A cleanup extension must not replace the adapter outcome.
127
+ }
128
+ }
129
+ }
130
+ }
77
131
  appendEvent(input.store, state, snapshot, "turn_completed", now, { summary: result.summary });
78
132
  if (result.status === "wait") {
79
133
  state = saveState(input.store, state, { status: "waiting", pending_barrier: result.barrier }, now);
@@ -87,6 +141,9 @@ export async function runContinuationDriver(input) {
87
141
  }
88
142
  snapshot = validateSnapshot(await input.runtime.synchronize());
89
143
  assertMissionIdentity(state, snapshot);
144
+ if (snapshot.status === "active" && snapshot.current_step === request.current_step) {
145
+ appendEvent(input.store, state, snapshot, "gate_not_advanced", now, { summary: "adapter completed without canonical gate advancement" });
146
+ }
90
147
  const outcome = canonicalOutcome(snapshot);
91
148
  if (outcome === "done")
92
149
  return finishDone(input.store, state, snapshot, now);
@@ -97,7 +154,11 @@ export async function runContinuationDriver(input) {
97
154
  return { outcome: "waiting", turns_started: state.turns_started, snapshot };
98
155
  }
99
156
  }
100
- state = saveState(input.store, state, { status: "budget_exhausted" }, now);
157
+ state = saveState(input.store, state, {
158
+ status: "budget_exhausted",
159
+ active_turn_step: null,
160
+ active_turn_public_key_digest: null,
161
+ }, now);
101
162
  appendEvent(input.store, state, snapshot, "budget_exhausted", now);
102
163
  return { outcome: "budget_exhausted", turns_started: state.turns_started, snapshot };
103
164
  }
@@ -115,6 +176,7 @@ export async function driveBuilderFlowSession(input) {
115
176
  store: createFileContinuationStore(sessionDir),
116
177
  ...(input.waitForBarrier ? { waitForBarrier: input.waitForBarrier } : {}),
117
178
  ...(input.authorizeTurn ? { authorizeTurn: input.authorizeTurn } : {}),
179
+ ...(input.issueTurnAuthority ? { issueTurnAuthority: input.issueTurnAuthority } : {}),
118
180
  ...(input.now ? { now: input.now } : {}),
119
181
  });
120
182
  }
@@ -125,10 +187,11 @@ export async function withContinuationDriverLock(sessionDirInput, body) {
125
187
  ensureSafeDirectory(sessionDir, locksDir);
126
188
  const token = randomUUID();
127
189
  const lockFile = path.join(locksDir, `${process.pid}-${token}.lock`);
128
- const owner = { schema_version: "1.0", pid: process.pid, token, created_at: new Date().toISOString() };
190
+ const lease = { pid: process.pid, token, created_at: new Date().toISOString() };
191
+ const owner = { schema_version: "1.0", ...lease };
129
192
  acquireDriverLock(locksDir, lockFile, owner);
130
193
  try {
131
- return await body();
194
+ return await body(lease);
132
195
  }
133
196
  finally {
134
197
  releaseDriverLock(lockFile, token);
@@ -274,6 +337,8 @@ function loadOrCreateState(store, snapshot, maxTurns, adapterCommandIdentity, no
274
337
  adapter_command_identity: adapterCommandIdentity,
275
338
  status: "active",
276
339
  turns_started: 0,
340
+ active_turn_step: null,
341
+ active_turn_public_key_digest: null,
277
342
  pending_barrier: null,
278
343
  updated_at: now().toISOString(),
279
344
  };
@@ -285,12 +350,22 @@ function canonicalOutcome(snapshot) {
285
350
  return snapshot.disposition;
286
351
  }
287
352
  function finishDone(store, state, snapshot, now) {
288
- const done = saveState(store, state, { status: "done", pending_barrier: null }, now);
353
+ const done = saveState(store, state, {
354
+ status: "done",
355
+ active_turn_step: null,
356
+ active_turn_public_key_digest: null,
357
+ pending_barrier: null,
358
+ }, now);
289
359
  appendEvent(store, done, snapshot, "done", now);
290
360
  return { outcome: "done", turns_started: done.turns_started, snapshot };
291
361
  }
292
362
  function finishFailed(store, state, snapshot, now) {
293
- const failed = saveState(store, state, { status: "failed", pending_barrier: null }, now);
363
+ const failed = saveState(store, state, {
364
+ status: "failed",
365
+ active_turn_step: null,
366
+ active_turn_public_key_digest: null,
367
+ pending_barrier: null,
368
+ }, now);
294
369
  return { outcome: "failed", turns_started: failed.turns_started, snapshot };
295
370
  }
296
371
  function saveState(store, current, patch, now) {
@@ -309,6 +384,7 @@ function appendEvent(store, state, snapshot, type, now, extra = {}) {
309
384
  at: now().toISOString(),
310
385
  ...(extra.barrier ? { barrier: extra.barrier } : {}),
311
386
  ...(extra.summary ? { summary: extra.summary } : {}),
387
+ ...(extra.failure_kind ? { failure_kind: extra.failure_kind } : {}),
312
388
  });
313
389
  }
314
390
  function validateSnapshot(value) {
@@ -364,6 +440,13 @@ function validateState(state) {
364
440
  throw new Error("continuation driver turns_started must be a non-negative integer");
365
441
  if (state.turns_started > state.max_turns)
366
442
  throw new Error("continuation driver turns_started cannot exceed max_turns");
443
+ if (state.active_turn_step !== undefined && state.active_turn_step !== null && (typeof state.active_turn_step !== "string" || state.active_turn_step.length === 0)) {
444
+ throw new Error("continuation driver active_turn_step must be a non-empty string or null");
445
+ }
446
+ if (state.active_turn_public_key_digest !== undefined && state.active_turn_public_key_digest !== null
447
+ && (typeof state.active_turn_public_key_digest !== "string" || !/^[a-f0-9]{64}$/.test(state.active_turn_public_key_digest))) {
448
+ throw new Error("continuation driver active_turn_public_key_digest must be a SHA-256 hex digest or null");
449
+ }
367
450
  if (!new Set(["active", "waiting", "done", "failed", "budget_exhausted"]).has(state.status))
368
451
  throw new Error("continuation driver state status is invalid");
369
452
  if (state.status === "budget_exhausted" && state.turns_started !== state.max_turns) {
@@ -2,8 +2,8 @@ export { BUILDER_BUILD_FLOW_ID, BUILDER_BUILD_FLOW_RELATIVE_PATH, BuilderBuildRu
2
2
  export type { BuilderBuildRunResult, BuilderBuildRunIdentityMismatch, BuilderBuildTrustBundleEvidenceInput, EvaluateBuilderBuildRunInput, LoadBuilderBuildRunInput, StartBuilderBuildRunInput, } from "./builder-flow-run-adapter.js";
3
3
  export { archiveBuilderFlowSession, cancelBuilderFlowSession, pauseBuilderFlowSession, recoverBuilderFlowSession, releaseBuilderFlowAssignment, resumeBuilderFlowSession, startBuilderFlowSession, syncBuilderFlowSession, } from "./builder-flow-runtime.js";
4
4
  export type { BuilderFlowAgentLifecycleInput, BuilderFlowAuthorizedLifecycleInput, BuilderFlowSessionInput, BuilderFlowSessionResult } from "./builder-flow-runtime.js";
5
- export { createFileContinuationStore, driveBuilderFlowSession, runContinuationDriver, withContinuationDriverLock, } from "./continuation-driver.js";
6
- export type { ContinuationBarrier, ContinuationDriverEvent, ContinuationDriverOutcome, ContinuationDriverState, ContinuationRuntimePort, ContinuationSnapshot, ContinuationStateStore, ContinuationTurnRequest, ContinuationTurnResult, DriveBuilderFlowSessionInput, RunContinuationDriverInput, } from "./continuation-driver.js";
5
+ export { ContinuationAdapterTimeoutError, createFileContinuationStore, driveBuilderFlowSession, runContinuationDriver, withContinuationDriverLock, } from "./continuation-driver.js";
6
+ export type { ContinuationBarrier, ContinuationDriverEvent, ContinuationDriverLockLease, ContinuationDriverOutcome, ContinuationDriverState, ContinuationRuntimePort, ContinuationSnapshot, ContinuationStateStore, ContinuationTurnAuthority, ContinuationTurnContext, ContinuationTurnRequest, ContinuationTurnResult, DriveBuilderFlowSessionInput, RunContinuationDriverInput, } from "./continuation-driver.js";
7
7
  export { builderLifecycleAuthorizationPayload, loadBuilderLifecycleAuthorization } from "./builder-lifecycle-authority.js";
8
8
  export type { BuilderLifecycleAuthorization } from "./builder-lifecycle-authority.js";
9
9
  export { defaultArtifactRootForRead, defaultCodexHome, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, durableFlowAgentsRoot, durableInstallRecordPath, DURABLE_FLOW_AGENTS_DIR, FLOW_AGENTS_RUNTIME_DIR, FLOW_AGENTS_RUNTIME_SUBDIR, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
@@ -15,7 +15,7 @@ import * as path from "node:path";
15
15
  import { loadJson as _loadJson, writeJson as _writeJson } from "./cli/workflow-sidecar.js";
16
16
  export { BUILDER_BUILD_FLOW_ID, BUILDER_BUILD_FLOW_RELATIVE_PATH, BuilderBuildRunInputError, BuilderBuildRunIdentityError, evaluateBuilderBuildRun, loadBuilderBuildRun, resolveBuilderBuildFlowDefinitionPath, startBuilderBuildRun, } from "./builder-flow-run-adapter.js";
17
17
  export { archiveBuilderFlowSession, cancelBuilderFlowSession, pauseBuilderFlowSession, recoverBuilderFlowSession, releaseBuilderFlowAssignment, resumeBuilderFlowSession, startBuilderFlowSession, syncBuilderFlowSession, } from "./builder-flow-runtime.js";
18
- export { createFileContinuationStore, driveBuilderFlowSession, runContinuationDriver, withContinuationDriverLock, } from "./continuation-driver.js";
18
+ export { ContinuationAdapterTimeoutError, createFileContinuationStore, driveBuilderFlowSession, runContinuationDriver, withContinuationDriverLock, } from "./continuation-driver.js";
19
19
  // Pure serialization contract used by external lifecycle authorities when
20
20
  // signing requests. This does not load, create, or mutate a Flow run.
21
21
  export { builderLifecycleAuthorizationPayload, loadBuilderLifecycleAuthorization } from "./builder-lifecycle-authority.js";
@@ -293,13 +293,30 @@ function appendExportNote(body, note) {
293
293
  function generatedAgentsSummary(agents) {
294
294
  return agents.slice().sort((a, b) => a.name.localeCompare(b.name)).map((spec) => `- \`${spec.name}\` — ${String(spec.description ?? "").trim()}`).join("\n");
295
295
  }
296
- function exportRootAgentsMd(label, agents, taskDir) {
297
- return `# Universal Agent Bundle (${label})\n\nThis bundle was generated from the canonical source in this repo. Treat the repo root as the source of truth and regenerate the bundle instead of editing exported agent files by hand.\n\n## Shared Conventions\n\n- \`skills/\`, \`context/\`, \`powers/\`, \`prompts/\`, \`scripts/\`, and \`evals/\` were copied from the canonical source.\n- Cross-session task artifacts should live under \`${taskDir}\`.\n- Kiro-only hook wiring was stripped from exported non-Kiro agents to keep the package portable.\n\n## Exported Agents\n\n${generatedAgentsSummary(agents)}\n`;
296
+ const BUNDLE_CAPABILITIES = {
297
+ "base": { label: "Base", installHint: "bash install.sh /path/to/workspace", instructionPath: "AGENTS.md", taskDir: runtimeTaskDir },
298
+ "kiro": { label: "Kiro", installHint: "bash install.sh $HOME/.flow-agents", instructionPath: "AGENTS.md", taskDir: runtimeTaskDir },
299
+ "claude-code": { label: "Claude Code", installHint: "bash install.sh /path/to/workspace", instructionPath: "CLAUDE.md", taskDir: manifest.claude_code.task_dir },
300
+ "codex": { label: "Codex", installHint: "bash install.sh /path/to/workspace", taskDir: manifest.codex.task_dir },
301
+ "opencode": { label: "opencode", installHint: "bash install.sh /path/to/workspace", instructionPath: "AGENTS.md", taskDir: manifest.opencode.task_dir },
302
+ "pi": { label: "pi", installHint: "bash install.sh /path/to/workspace", instructionPath: "AGENTS.md", taskDir: manifest.pi.task_dir },
303
+ };
304
+ function renderOperationalInstructions(capability) {
305
+ return `# Flow Agents\n\nUse the installed Flow Agents skills and runtime integrations for delivery work. Keep cross-session task artifacts under \`${capability.taskDir}\`.\n`;
298
306
  }
299
307
  const CODEX_LIVE_HOOKS_README = `\n## Running with hooks active (live)\n\n\`install.sh\` lays the bundle into a workspace, but a live Codex session needs a \`CODEX_HOME\` that has both the bundle's hooks/scripts AND your real credentials. Use the dedicated installer, which defaults to \`CODEX_HOME\` when set and otherwise \`~/.codex\`, flattens the config to the home root, and copies your auth from \`~/.codex\` when installing elsewhere:\n\n\`\`\`bash\nbash scripts/install-codex-home.sh\ncodex exec --dangerously-bypass-hook-trust -C /path/to/project \"<prompt>\"\n\`\`\`\n\nPass a positional destination to install into an explicit alternate Codex home.\n\nThe goal-fit Stop hook then enforces by default (\`FLOW_AGENTS_GOAL_FIT_MODE=block\`); set it to \`warn\` or \`off\` to override.\n`;
300
308
  function exportTargetReadme(label, installHint, extra = "") {
301
309
  return `# ${label} Bundle\n\nGenerated from the canonical source in this repository.\n\n## Install\n\n\`\`\`bash\n${installHint}\n\`\`\`\n\nThe install ships the full standalone base (skills, agents, powers) plus the\nFlow Kits. Kit depth is activated through the Kit Catalog, not at install time.\n\n## Contents\n\n- Harness-specific agents\n- Shared skills\n- Shared context, powers, prompts, scripts, and evals\n${extra}`;
302
310
  }
311
+ function renderBundleReadme(capability, agents, extra = "") {
312
+ const inventory = `\n## Exported agents\n\nThe following agent definitions were generated from the canonical agent specifications in this repository. This inventory is documentation, not agent instructions.\n\n${generatedAgentsSummary(agents)}\n`;
313
+ return exportTargetReadme(capability.label, capability.installHint, `${inventory}${extra}`);
314
+ }
315
+ function writeBundlePolicy(bundle, capability, agents, readmeExtra = "") {
316
+ writeText(path.join(bundle, "README.md"), renderBundleReadme(capability, agents, readmeExtra));
317
+ if (capability.instructionPath)
318
+ writeText(path.join(bundle, capability.instructionPath), renderOperationalInstructions(capability));
319
+ }
303
320
  function mapClaudeTools(allowedTools) {
304
321
  const ordered = [];
305
322
  for (const tool of Array.isArray(allowedTools) ? allowedTools : []) {
@@ -451,48 +468,59 @@ function copySharedContent(targetRoot, targetName, token) {
451
468
  }
452
469
  for (const dir of manifest.optional_copy_dirs ?? [])
453
470
  copyTree(path.join(root, dir), path.join(targetRoot, dir), targetName, token);
454
- writeText(path.join(targetRoot, "build/package.json"), `${JSON.stringify({ type: "module" }, null, 2)}\n`);
471
+ writeText(path.join(targetRoot, "build/package.json"), `${JSON.stringify({
472
+ name: "@kontourai/flow-agents",
473
+ version: pkgVersion,
474
+ type: "module",
475
+ }, null, 2)}\n`);
455
476
  const commonBuilt = path.join(root, "build/src/tools/common.js");
456
477
  if (fs.existsSync(commonBuilt))
457
478
  writeText(path.join(targetRoot, "scripts/common.mjs"), readText(commonBuilt));
458
479
  copyTree(path.join(root, "build/src"), path.join(targetRoot, "build/src"), targetName, token);
459
480
  }
460
- function installScript(label, defaultDestDisplay, token, destFallbackShell, mergeConfig, stampConfig) {
461
- const replaceBlock = token ? `\nexport DEST\nfind "$DEST" -type f \\( -name '*.json' -o -name '*.md' -o -name '*.sh' -o -name '*.js' -o -name '*.ts' -o -name '*.yaml' -o -name '*.yml' \\) -print0 | xargs -0 perl -0pi -e 's#${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}#$ENV{DEST}#g'` : "";
481
+ function installScript(label, capability, defaultDestDisplay, token, destFallbackShell, mergeConfig, stampConfig) {
482
+ const replaceBlock = token ? `\nexport DEST\nfind "$DEST" \\( -path "$DEST/AGENTS.md" -o -path "$DEST/CLAUDE.md" \\) -prune -o -type f \\( -name '*.json' -o -name '*.md' -o -name '*.sh' -o -name '*.js' -o -name '*.ts' -o -name '*.yaml' -o -name '*.yml' \\) -print0 | xargs -0 perl -0pi -e 's#${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}#$ENV{DEST}#g'` : "";
462
483
  const destFallback = destFallbackShell ? `\nif [[ -z "$DEST" ]]; then\n DEST="${destFallbackShell}"\nfi` : "";
463
484
  const destRequired = !destFallbackShell;
464
- const requiredCheck = destRequired ? `if [[ -z "$DEST" ]]; then\n usage\n exit 2\nfi\n` : "";
485
+ const requiredCheck = `${destRequired ? `if [[ -z "$DEST" ]]; then\n usage\n exit 2\nfi\n` : ""}while [[ "$DEST" != "/" && "$DEST" == */ ]]; do\n DEST="\${DEST%/}"\ndone\n`;
465
486
  const usageDest = destRequired ? "/path/to/workspace" : defaultDestDisplay;
466
487
  const mergeBlock = mergeConfig
467
488
  ? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --config "$DEST/${mergeConfig.configRelPath}" --managed-hooks "$SRC/${mergeConfig.managedConfigRelPath}" --version "${mergeConfig.version}" --install-record "$DEST/${durableInstallRecordRel}" --runtime "${mergeConfig.runtime}" || true\nfi`
468
489
  : stampConfig
469
490
  ? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --stamp-only --version "${stampConfig.version}" --install-record "$DEST/${durableInstallRecordRel}" --runtime "${stampConfig.runtime}" || true\nfi`
470
491
  : "";
471
- return `#!/usr/bin/env bash\nset -euo pipefail\n\nusage() {\n cat >&2 <<'EOF'\nusage: bash install.sh ${usageDest} [options]\n\nOptions:\n --telemetry-sink NAME local-files, local-kontour-console,\n kontour-hosted-console, user-hosted-console,\n or legacy aliases. May be repeated.\n --console-url URL Persist Console telemetry base URL.\n --console-endpoint URL Persist full Console telemetry records endpoint URL.\n --console-token-file PATH\n Read Console telemetry bearer token from a file.\n --console-tenant ID Persist Console tenant identifier.\nEOF\n}\n\nDEST=""\nDEST_SET=0\nCONSOLE_CONFIG_ARGS=()\nwhile [[ $# -gt 0 ]]; do\n case "$1" in\n --telemetry-sink|--telemetry-sinks|--console-url|--console-endpoint|--console-endpoint-url|--console-token-file|--console-tenant|--console-tenant-id)\n [[ $# -ge 2 ]] || { echo "install.sh: $1 requires a value" >&2; exit 2; }\n CONSOLE_CONFIG_ARGS+=("$1" "$2")\n shift 2\n ;;\n --help|-h)\n usage\n exit 0\n ;;\n -*)\n echo "install.sh: unknown option: $1" >&2\n usage\n exit 2\n ;;\n *)\n if [[ "$DEST_SET" -eq 1 ]]; then\n echo "install.sh: unexpected argument: $1" >&2\n usage\n exit 2\n fi\n DEST="$1"\n DEST_SET=1\n shift\n ;;\n esac\ndone${destFallback}\n${requiredCheck}SRC="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"\n\nmkdir -p "$DEST"\nrsync -a ${token ? "--delete " : ""}${mergeConfig ? `--exclude="${mergeConfig.configRelPath}" ` : ""}"$SRC"/ "$DEST"/${replaceBlock}${mergeBlock}\nif [[ \${#CONSOLE_CONFIG_ARGS[@]} -gt 0 || -n "\${FLOW_AGENTS_TELEMETRY_SINK:-}" || -n "\${FLOW_AGENTS_TELEMETRY_SINKS:-}" || -n "\${FLOW_AGENTS_CONSOLE_URL:-}" || -n "\${CONSOLE_TELEMETRY_URL:-}" || -n "\${CONSOLE_URL:-}" || -n "\${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-}" || -n "\${CONSOLE_TELEMETRY_TOKEN_FILE:-}" ]]; then\n bash "$DEST/scripts/telemetry/install-console-config.sh" "$DEST/scripts/telemetry/telemetry.conf" "\${CONSOLE_CONFIG_ARGS[@]}"\nfi\necho "Installed ${label} bundle ${token ? "to" : "into"} $DEST"\n`;
492
+ // Excluding both discovery filenames protects any existing filesystem
493
+ // object, including during Kiro's --delete sync. Required guidance is then
494
+ // published with a no-overwrite hard link from a destination-local temp file.
495
+ const instructionBlock = capability.instructionPath
496
+ ? `\nif [[ ! -e "$DEST/${capability.instructionPath}" && ! -L "$DEST/${capability.instructionPath}" ]]; then\n instruction_tmp="$(mktemp "$DEST/.flow-agents-instructions.XXXXXX")"\n cp "$SRC/${capability.instructionPath}" "$instruction_tmp"\n if ! ln "$instruction_tmp" "$DEST/${capability.instructionPath}" 2>/dev/null; then\n if [[ ! -e "$DEST/${capability.instructionPath}" && ! -L "$DEST/${capability.instructionPath}" ]]; then\n rm -f "$instruction_tmp"\n echo "install.sh: could not safely install ${capability.instructionPath}" >&2\n exit 1\n fi\n fi\n rm -f "$instruction_tmp"\nfi`
497
+ : "";
498
+ return `#!/usr/bin/env bash\nset -euo pipefail\n\nusage() {\n cat >&2 <<'EOF'\nusage: bash install.sh ${usageDest} [options]\n\nOptions:\n --telemetry-sink NAME local-files, local-kontour-console,\n kontour-hosted-console, user-hosted-console,\n or legacy aliases. May be repeated.\n --console-url URL Persist Console telemetry base URL.\n --console-endpoint URL Persist full Console telemetry records endpoint URL.\n --console-token-file PATH\n Read Console telemetry bearer token from a file.\n --console-tenant ID Persist Console tenant identifier.\nEOF\n}\n\nDEST=""\nDEST_SET=0\nCONSOLE_CONFIG_ARGS=()\nwhile [[ $# -gt 0 ]]; do\n case "$1" in\n --telemetry-sink|--telemetry-sinks|--console-url|--console-endpoint|--console-endpoint-url|--console-token-file|--console-tenant|--console-tenant-id)\n [[ $# -ge 2 ]] || { echo "install.sh: $1 requires a value" >&2; exit 2; }\n CONSOLE_CONFIG_ARGS+=("$1" "$2")\n shift 2\n ;;\n --help|-h)\n usage\n exit 0\n ;;\n -*)\n echo "install.sh: unknown option: $1" >&2\n usage\n exit 2\n ;;\n *)\n if [[ "$DEST_SET" -eq 1 ]]; then\n echo "install.sh: unexpected argument: $1" >&2\n usage\n exit 2\n fi\n DEST="$1"\n DEST_SET=1\n shift\n ;;\n esac\ndone${destFallback}\n${requiredCheck}SRC="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"\n\nmkdir -p "$DEST"\nrsync -a ${token ? "--delete " : ""}--exclude='/AGENTS.md' --exclude='/CLAUDE.md' ${mergeConfig ? `--exclude="${mergeConfig.configRelPath}" ` : ""}"$SRC"/ "$DEST"/${instructionBlock}${replaceBlock}${mergeBlock}\nif [[ \${#CONSOLE_CONFIG_ARGS[@]} -gt 0 || -n "\${FLOW_AGENTS_TELEMETRY_SINK:-}" || -n "\${FLOW_AGENTS_TELEMETRY_SINKS:-}" || -n "\${FLOW_AGENTS_CONSOLE_URL:-}" || -n "\${CONSOLE_TELEMETRY_URL:-}" || -n "\${CONSOLE_URL:-}" || -n "\${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-}" || -n "\${CONSOLE_TELEMETRY_TOKEN_FILE:-}" ]]; then\n bash "$DEST/scripts/telemetry/install-console-config.sh" "$DEST/scripts/telemetry/telemetry.conf" "\${CONSOLE_CONFIG_ARGS[@]}"\nfi\necho "Installed ${label} bundle ${token ? "to" : "into"} $DEST"\n`;
472
499
  }
473
500
  function buildBase(agents) {
501
+ const capability = BUNDLE_CAPABILITIES["base"];
474
502
  const bundle = path.join(dist, "base");
475
503
  resetDir(bundle);
476
504
  copySharedContent(bundle, "base", "<bundle-root>");
477
505
  writeText(path.join(bundle, runtimeTaskDir, ".gitkeep"), "");
478
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Base", agents, runtimeTaskDir));
479
- writeText(path.join(bundle, "README.md"), exportTargetReadme("Base", "bash install.sh /path/to/workspace"));
480
- writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace", undefined, undefined, undefined, { runtime: "base", version: pkgVersion }));
506
+ writeBundlePolicy(bundle, capability, agents);
507
+ writeText(path.join(bundle, "install.sh"), installScript("Base", capability, "/path/to/workspace", undefined, undefined, undefined, { runtime: "base", version: pkgVersion }));
481
508
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
482
509
  }
483
510
  function buildKiro(agents) {
511
+ const capability = BUNDLE_CAPABILITIES["kiro"];
484
512
  const bundle = path.join(dist, "kiro");
485
513
  const token = manifest.kiro.path_token;
486
514
  resetDir(bundle);
487
515
  copySharedContent(bundle, "kiro", token);
488
516
  for (const spec of agents)
489
517
  writeText(path.join(bundle, "agents", `${spec.name}.json`), sanitizeText(`${JSON.stringify(sanitizeAgentJson(spec), null, 2)}\n`, "kiro", token));
490
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Kiro", agents, runtimeTaskDir));
491
- writeText(path.join(bundle, "README.md"), exportTargetReadme("Kiro", "bash install.sh $HOME/.flow-agents"));
492
- writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}', undefined, { runtime: "kiro", version: pkgVersion }));
518
+ writeBundlePolicy(bundle, capability, agents);
519
+ writeText(path.join(bundle, "install.sh"), installScript("Kiro", capability, "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}', undefined, { runtime: "kiro", version: pkgVersion }));
493
520
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
494
521
  }
495
522
  function buildClaudeCode(agents) {
523
+ const capability = BUNDLE_CAPABILITIES["claude-code"];
496
524
  const bundle = path.join(dist, "claude-code");
497
525
  resetDir(bundle);
498
526
  copySharedContent(bundle, "claude-code", "<bundle-root>");
@@ -503,12 +531,12 @@ function buildClaudeCode(agents) {
503
531
  writeText(path.join(bundle, ".claude/skills", name, "SKILL.md"), sanitizeText(readText(src), "claude-code", "<bundle-root>"));
504
532
  }
505
533
  writeText(path.join(bundle, ".claude/settings.json"), exportClaudeSettings());
506
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Claude Code", agents, manifest.claude_code.task_dir));
507
- writeText(path.join(bundle, "README.md"), exportTargetReadme("Claude Code", "bash install.sh /path/to/workspace"));
508
- writeText(path.join(bundle, "install.sh"), installScript("Claude Code", "/path/to/workspace", undefined, undefined, { configRelPath: ".claude/settings.json", managedConfigRelPath: ".claude/settings.json", runtime: "claude-code", version: pkgVersion }));
534
+ writeBundlePolicy(bundle, capability, agents);
535
+ writeText(path.join(bundle, "install.sh"), installScript("Claude Code", capability, "/path/to/workspace", undefined, undefined, { configRelPath: ".claude/settings.json", managedConfigRelPath: ".claude/settings.json", runtime: "claude-code", version: pkgVersion }));
509
536
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
510
537
  }
511
538
  function buildCodex(agents) {
539
+ const capability = BUNDLE_CAPABILITIES["codex"];
512
540
  const bundle = path.join(dist, "codex");
513
541
  const excluded = new Set(manifest.codex.excluded_agents ?? []);
514
542
  const targetAgents = agents.filter((spec) => !excluded.has(spec.name));
@@ -526,9 +554,8 @@ function buildCodex(agents) {
526
554
  for (const { name, src } of collectAllSkills())
527
555
  exportSkillPackage(src, path.join(skillsRoot, name), "codex");
528
556
  validateExportedSkillPackages(skillsRoot);
529
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Codex", targetAgents, manifest.codex.task_dir));
530
- writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace", CODEX_LIVE_HOOKS_README));
531
- writeText(path.join(bundle, "install.sh"), installScript("Codex", "/path/to/workspace", undefined, undefined, { configRelPath: ".codex/hooks.json", managedConfigRelPath: ".codex/hooks.json", runtime: "codex", version: pkgVersion }));
557
+ writeBundlePolicy(bundle, capability, targetAgents, CODEX_LIVE_HOOKS_README);
558
+ writeText(path.join(bundle, "install.sh"), installScript("Codex", capability, "/path/to/workspace", undefined, undefined, { configRelPath: ".codex/hooks.json", managedConfigRelPath: ".codex/hooks.json", runtime: "codex", version: pkgVersion }));
532
559
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
533
560
  }
534
561
  function exportOpencodeAgent(spec) {
@@ -696,6 +723,7 @@ function exportOpencodeConfig() {
696
723
  }, null, 2)}\n`;
697
724
  }
698
725
  function buildOpencode(agents) {
726
+ const capability = BUNDLE_CAPABILITIES["opencode"];
699
727
  const bundle = path.join(dist, "opencode");
700
728
  resetDir(bundle);
701
729
  copySharedContent(bundle, "opencode", "<bundle-root>");
@@ -708,9 +736,8 @@ function buildOpencode(agents) {
708
736
  }
709
737
  writeText(path.join(bundle, ".opencode/plugins/flow-agents.js"), exportOpencodePlugin());
710
738
  writeText(path.join(bundle, "opencode.json"), exportOpencodeConfig());
711
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("opencode", agents, manifest.opencode.task_dir));
712
- writeText(path.join(bundle, "README.md"), exportTargetReadme("opencode", "bash install.sh /path/to/workspace"));
713
- writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace", undefined, undefined, { configRelPath: "opencode.json", managedConfigRelPath: "opencode.json", runtime: "opencode", version: pkgVersion }));
739
+ writeBundlePolicy(bundle, capability, agents);
740
+ writeText(path.join(bundle, "install.sh"), installScript("opencode", capability, "/path/to/workspace", undefined, undefined, { configRelPath: "opencode.json", managedConfigRelPath: "opencode.json", runtime: "opencode", version: pkgVersion }));
714
741
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
715
742
  }
716
743
  function exportPiExtension() {
@@ -809,6 +836,7 @@ export default function (pi: ExtensionAPI) {
809
836
  `;
810
837
  }
811
838
  function buildPi(agents) {
839
+ const capability = BUNDLE_CAPABILITIES["pi"];
812
840
  const bundle = path.join(dist, "pi");
813
841
  resetDir(bundle);
814
842
  copySharedContent(bundle, "pi", "<bundle-root>");
@@ -819,9 +847,8 @@ function buildPi(agents) {
819
847
  writeText(path.join(bundle, ".pi/skills", name, "SKILL.md"), sanitizeText(readText(src), "pi", "<bundle-root>"));
820
848
  }
821
849
  writeText(path.join(bundle, ".pi/extensions/flow-agents.ts"), exportPiExtension());
822
- writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("pi", agents, manifest.pi.task_dir));
823
- writeText(path.join(bundle, "README.md"), exportTargetReadme("pi", "bash install.sh /path/to/workspace"));
824
- writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace", undefined, undefined, undefined, { runtime: "pi", version: pkgVersion }));
850
+ writeBundlePolicy(bundle, capability, agents);
851
+ writeText(path.join(bundle, "install.sh"), installScript("pi", capability, "/path/to/workspace", undefined, undefined, undefined, { runtime: "pi", version: pkgVersion }));
825
852
  fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
826
853
  }
827
854
  function buildCatalog(agents) {
@@ -32,6 +32,7 @@ const mirroredFiles = new Map([
32
32
  // fails `npm run validate:source` instead of silently accumulating.
33
33
  ["scripts/hooks/config-protection.js", { mirror: "context/scripts/hooks/config-protection.js", allowedDifferences: [] }],
34
34
  ["scripts/hooks/lib/config-protection-remedies.js", { mirror: "context/scripts/hooks/lib/config-protection-remedies.js", allowedDifferences: [] }],
35
+ ["scripts/hooks/lib/continuation-turn-authority.js", { mirror: "context/scripts/hooks/lib/continuation-turn-authority.js", allowedDifferences: [] }],
35
36
  ["scripts/hooks/lib/kit-catalog.js", { mirror: "context/scripts/hooks/lib/kit-catalog.js", allowedDifferences: [] }],
36
37
  ["scripts/hooks/lib/runnable-command.js", { mirror: "context/scripts/hooks/lib/runnable-command.js", allowedDifferences: [] }],
37
38
  ["scripts/hooks/stop-goal-fit.js", { mirror: "context/scripts/hooks/stop-goal-fit.js", allowedDifferences: [] }],
@@ -83,10 +84,11 @@ const hookFilePolicies = new Map([
83
84
  ["scripts/hooks/utterance-check.js", { category: "policy hook", requiredNeedles: ["Utterance Check Hook", "FLOW_AGENTS_UTTERANCE_CHECK_ENABLED"] }],
84
85
  ["scripts/hooks/workflow-steering.js", { category: "policy hook", requiredNeedles: ["Workflow Steering Hook"] }],
85
86
  ["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
86
- ["scripts/hooks/lib/actor-identity.js", { category: "shared hook library", requiredNeedles: ["resolveActor", "FLOW_AGENTS_ACTOR"] }],
87
+ ["scripts/hooks/lib/actor-identity.js", { category: "shared hook library", requiredNeedles: ["resolveActor", "FLOW_AGENTS_ACTOR", "CODEX_THREAD_ID", "flow-agents:codex-thread:v1:"] }],
87
88
  ["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
88
89
  ["scripts/hooks/lib/codex-exit-code.js", { category: "shared hook library", requiredNeedles: ["extractExitCodeFromBanner", "readExitCodeFromRollout"] }],
89
90
  ["scripts/hooks/lib/config-protection-remedies.js", { category: "shared hook library", requiredNeedles: ["SANCTIONED_REMEDIES", "REMEDY_COMMAND_CANDIDATES"] }],
91
+ ["scripts/hooks/lib/continuation-turn-authority.js", { category: "shared hook library", requiredNeedles: ["issueActiveTurnAuthority", "validateActiveTurnAuthority", "validateSignedActiveTurnAssignmentAuthority"] }],
90
92
  ["scripts/hooks/lib/current-pointer.js", { category: "shared hook library", requiredNeedles: ["readCurrentPointer", "perActorCurrentFile"] }],
91
93
  ["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
92
94
  ["scripts/hooks/lib/kit-catalog.js", { category: "shared hook library", requiredNeedles: ["readKitManifests", "workflowTriggersFor"] }],
@@ -105,6 +107,7 @@ const fixtureOwnerPolicies = new Map([
105
107
  ["evals/fixtures/backlog-provider-settings", { owners: ["evals/integration/test_effective_backlog_settings.sh"], classification: "settings precedence fixtures" }],
106
108
  ["evals/fixtures/builder-kit-workflow-state", { owners: ["evals/static/test_workflow_skills.sh"], classification: "Builder Kit workflow-state fixtures" }],
107
109
  ["evals/fixtures/console-learning-projection", { owners: ["evals/integration/test_console_learning_projection.sh"], classification: "console learning projection fixtures" }],
110
+ ["evals/fixtures/codex-legacy-agents", { owners: ["evals/integration/test_install_merge.sh"], classification: "exact historical Flow Agents-generated Codex AGENTS.md migration fixtures (#563)" }],
108
111
  ["evals/fixtures/economics", { owners: ["evals/integration/test_economics_record.sh"], classification: "per-run kit-economics record fixtures (#349): a transcript with .message.usage blocks, state.json/acceptance.json/critique.json join sources, a session.usage event, and the golden expected kontour.console.economics record" }],
109
112
  ["evals/fixtures/flow-kit-repository", { owners: ["evals/integration/test_flow_kit_repository.sh", "evals/integration/test_local_flow_kit_install.sh", "evals/integration/test_runtime_adapter_activation.sh", "evals/integration/test_activate_npx_context.sh", "evals/integration/test_flow_kit_install_git.sh", "evals/static/test_workflow_skills.sh"], classification: "Flow Kit repository contract fixtures" }],
110
113
  ["evals/fixtures/kit-conformance-levels", { owners: ["evals/integration/test_kit_conformance_levels.sh"], classification: "K-level conformance and consumer-target derivation fixtures" }],
@@ -113,7 +116,7 @@ const fixtureOwnerPolicies = new Map([
113
116
  ["evals/fixtures/pull-work-provider", { owners: ["evals/integration/test_pull_work_provider.sh"], classification: "work item provider normalization fixtures" }],
114
117
  ["evals/fixtures/pull-work-wip-shepherding", { owners: ["evals/static/test_workflow_skills.sh"], classification: "WIP shepherding state fixtures" }],
115
118
  ["evals/fixtures/surface-trust", { owners: ["evals/integration/test_workflow_sidecar_writer.sh"], classification: "Surface trust evidence fixtures" }],
116
- ["evals/fixtures/telemetry", { owners: ["evals/integration/test_usage_cost.sh", "evals/integration/test_telemetry_usage_pipeline.sh"], classification: "hermetic Stop-hook usage transcript fixture (#defect-2026-07 telemetry-usage-cost-extraction): a multi-model JSONL transcript with .message.model + .message.usage token blocks proving real token/cost/model extraction end-to-end" }],
119
+ ["evals/fixtures/telemetry", { owners: ["evals/integration/test_usage_cost.sh", "evals/integration/test_telemetry_usage_pipeline.sh", "evals/integration/test_telemetry_tool_usage.sh"], classification: "hermetic Stop-hook usage transcript fixture (#defect-2026-07 telemetry-usage-cost-extraction): a multi-model JSONL transcript with .message.model + .message.usage token blocks proving real token/cost/model extraction end-to-end; also reused by the #568 slice 1 per-tool-call usage-enrichment eval (test_telemetry_tool_usage.sh), which adds usage-transcript-oversized-prefix.jsonl to prove the bounded tail-read" }],
117
120
  ["evals/fixtures/reconcile-preflight", { owners: ["evals/integration/test_reconcile_preflight.sh"], classification: "#356 reconcile-preflight shape fixtures not already covered by trust-reconcile-exploits (un-superseded disputed critique, standalone disputed session-local claim)" }],
118
121
  ["evals/fixtures/trust-reconcile-exploits", { owners: ["evals/integration/test_trust_reconcile_negatives.sh", "evals/integration/test_reconcile_preflight.sh"], classification: "WS8 trust-reconcile anti-gaming exploit fixtures (frozen negative regressions); also reused by the #356 local reconcile-preflight eval (same shapes, no forked copies)" }],
119
122
  ["evals/fixtures/trust-reconcile-mixed-bundle", { owners: ["evals/integration/test_trust_reconcile_mixed_bundle.sh", "evals/integration/test_reconcile_preflight.sh"], classification: "WS8 trust-reconcile mixed-evidence end-to-end proof fixture; also reused by the #356 reconcile-preflight eval as its CLEAN-BUNDLE (AC4) case" }],