@bridge_gpt/mcp-server 0.2.57 → 0.2.59

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 (90) hide show
  1. package/README.md +112 -113
  2. package/build/agent-registry.js +15 -0
  3. package/build/agent-utils.js +16 -8
  4. package/build/claude-user-config-doctor.js +1 -35
  5. package/build/codex-bridge-profile.js +5 -1
  6. package/build/codex-launch-scope.js +13 -2
  7. package/build/commands.generated.js +5 -6
  8. package/build/conductor/bridge-api-client.js +149 -0
  9. package/build/conductor/bring-up-facts.js +1 -1
  10. package/build/conductor/claude-hook.js +16 -16
  11. package/build/conductor/doctor.js +24 -19
  12. package/build/conductor/epic-implementer-cli.js +38 -5
  13. package/build/conductor/epic-reconcile.js +1 -1
  14. package/build/conductor/epic-runtime.js +10 -10
  15. package/build/conductor/errors.js +3 -3
  16. package/build/conductor/file-scope-guard.js +4 -4
  17. package/build/conductor/git-ci-types.js +1 -1
  18. package/build/conductor/git-inspection.js +2 -2
  19. package/build/conductor/install-doctor.js +6 -6
  20. package/build/conductor/merge-ledger.js +2 -2
  21. package/build/conductor/pr-ci-producer.js +6 -6
  22. package/build/conductor/pr-review-producer.js +2 -2
  23. package/build/conductor/producer-ledger.js +2 -0
  24. package/build/conductor/readiness-sections.js +7 -7
  25. package/build/conductor/readiness.js +2 -2
  26. package/build/conductor/recovery-cli.js +243 -12
  27. package/build/conductor/recovery-operations.js +56 -4
  28. package/build/conductor/repair-contract.js +38 -4
  29. package/build/conductor/spec-review-producer.js +6 -6
  30. package/build/conductor/store.js +10 -10
  31. package/build/conductor/supervisor-config.js +14 -14
  32. package/build/conductor/supervisor-ledger.js +2 -2
  33. package/build/conductor/supervisor-message-relay.js +2 -2
  34. package/build/conductor/worker-ledger-cli.js +19 -19
  35. package/build/conductor-bundle-artifacts.js +2 -2
  36. package/build/cursor-agent-progress.js +72 -0
  37. package/build/doctor.js +20 -296
  38. package/build/drive-epic.js +65 -71
  39. package/build/epic-implementer-bin.js +22 -17
  40. package/build/epic-implementer-claude-hook-bin.js +4 -2
  41. package/build/epic-implementer-env.js +155 -0
  42. package/build/epic-integration-pr.js +2 -2
  43. package/build/epic-shared/bridge-client.js +773 -0
  44. package/build/epic-shared/cut-protocol.js +392 -0
  45. package/build/executor/env.js +19 -8
  46. package/build/executor/heartbeat.js +1 -1
  47. package/build/executor/job-errors.js +3 -3
  48. package/build/executor/job-runner.js +1 -1
  49. package/build/executor/merge-job.js +35 -3
  50. package/build/executor/observation.js +1 -1
  51. package/build/executor/preflight.js +1 -1
  52. package/build/executor/prompt-spec.js +1 -1
  53. package/build/executor/reconcile-overlap-job.js +10 -130
  54. package/build/executor/reconcile-overlap-types.js +1 -1
  55. package/build/executor/runner.js +1 -1
  56. package/build/implement-epic/cli.js +1 -1
  57. package/build/index.js +187 -224
  58. package/build/init.js +20 -28
  59. package/build/install-bridge-conductor.js +7 -7
  60. package/build/install-bridge.js +203 -10
  61. package/build/install-doctor.js +1 -1
  62. package/build/mcp-host-entry-adapter.js +6 -0
  63. package/build/mcp-host-targets.js +2 -0
  64. package/build/mcp-invoke.js +5 -5
  65. package/build/mcp-profile.js +2 -2
  66. package/build/mcp-provisioning.js +2 -2
  67. package/build/mcp-server-invocation.js +3 -3
  68. package/build/merge-pull-request.js +15 -0
  69. package/build/model-routing-host.js +25 -0
  70. package/build/model-routing-preferences.js +99 -0
  71. package/build/pipelines.generated.js +11 -11
  72. package/build/plan-epic-implementer-eligibility.js +215 -0
  73. package/build/plan-execution-ledger.js +8 -0
  74. package/build/plane/member-roster.js +5 -5
  75. package/build/plane/supervisor.js +1 -1
  76. package/build/plane/types.js +5 -5
  77. package/build/readiness-check.js +1 -1
  78. package/build/readme.generated.js +1 -1
  79. package/build/review-tickets.js +4 -4
  80. package/build/setup-epic.js +8 -8
  81. package/build/start-tickets-conductor.js +32 -32
  82. package/build/start-tickets-prereqs.js +4 -9
  83. package/build/start-tickets.js +494 -98
  84. package/build/version.generated.js +3 -3
  85. package/docs/CONDUCTOR.md +69 -18
  86. package/docs/install/mcp-tool-integrations.md +2 -1
  87. package/package.json +2 -2
  88. package/pipelines/implement-ticket.json +2 -2
  89. package/pipelines/plan-epic.json +2 -2
  90. package/pipelines/review-ticket.json +1 -1
@@ -1,10 +1,11 @@
1
1
  /**
2
- * The shared contract for the run-addressed operator repair verbs (BAPI-1154):
3
- * `scope-reparse`, `dispatch-remediation`, and `merge-child`.
2
+ * The shared contract for the six run-addressed operator repair verbs: BAPI-1154's
3
+ * `scope-reparse`, `dispatch-remediation`, and `merge-child`, and BAPI-1176's
4
+ * `scope-catch-up`, `patch-policy`, and `resume-run`.
4
5
  *
5
6
  * CLI-only, exactly like the BAPI-872 recovery verbs: none of these names is ever
6
7
  * registered as an MCP tool (`index-static.test.ts` pins that). This module is the
7
- * TypeScript mirror of `api/library/epic_conductor/operator_repair_contract.py` —
8
+ * TypeScript mirror of `api/library/epic_implementer/operator_repair_contract.py` —
8
9
  * the verb names, the CLOSED refusal-code table, the driver-context byte cap, and
9
10
  * the bounded result shapes the Bridge client returns. A parity test keeps the two
10
11
  * aligned; the CLI renders the server's code and never infers ownership or state
@@ -16,11 +17,17 @@
16
17
  export const VERB_SCOPE_REPARSE = "scope-reparse";
17
18
  export const VERB_DISPATCH_REMEDIATION = "dispatch-remediation";
18
19
  export const VERB_MERGE_CHILD = "merge-child";
20
+ export const VERB_SCOPE_CATCH_UP = "scope-catch-up";
21
+ export const VERB_PATCH_POLICY = "patch-policy";
22
+ export const VERB_RESUME_RUN = "resume-run";
19
23
  /** Every repair verb, in the order an operator meets them. */
20
24
  export const OPERATOR_REPAIR_VERBS = [
21
25
  VERB_SCOPE_REPARSE,
22
26
  VERB_DISPATCH_REMEDIATION,
23
27
  VERB_MERGE_CHILD,
28
+ VERB_SCOPE_CATCH_UP,
29
+ VERB_PATCH_POLICY,
30
+ VERB_RESUME_RUN,
24
31
  ];
25
32
  /**
26
33
  * The ONE cap on driver context, in UTF-8 BYTES — the same number the server
@@ -29,7 +36,24 @@ export const OPERATOR_REPAIR_VERBS = [
29
36
  * see the operator's complete instruction or none of it.
30
37
  */
31
38
  export const DRIVER_CONTEXT_MAX_BYTES = 16384;
32
- /** The closed refusal-code vocabulary, mirroring the server table. */
39
+ /**
40
+ * The ONE cap on a `patch-policy` merge patch, in UTF-8 BYTES — the same number the
41
+ * server enforces (`POLICY_PATCH_MAX_BYTES` in `operator_repair_contract.py`). The
42
+ * CLI checks the patch file's raw bytes before decoding it, so an over-cap patch is
43
+ * never parsed, printed, or transmitted.
44
+ */
45
+ export const POLICY_PATCH_MAX_BYTES = 16384;
46
+ /**
47
+ * The closed refusal-code vocabulary, mirroring the server table.
48
+ *
49
+ * Deliberately NOT every code a repair verb can render. `patch-policy` and
50
+ * `resume-run` run the server's established posture and unattended-activation
51
+ * checks, and `resume-run` its run-control validation; those refuse with their OWN
52
+ * structured codes (for example `RUN_CONTROL_MALFORMED`). Such pass-through codes
53
+ * are not members of this table, yet the CLI renders them exactly like the closed
54
+ * codes: every refusal is classified from the server's structured `error_code`,
55
+ * never from this list and never from error prose.
56
+ */
33
57
  export const OPERATOR_REPAIR_REFUSAL_CODES = [
34
58
  "TICKET_NOT_IN_RUN",
35
59
  "DRIVER_CONTEXT_INVALID",
@@ -50,6 +74,16 @@ export const OPERATOR_REPAIR_REFUSAL_CODES = [
50
74
  "MERGE_GATE_UNRESOLVABLE",
51
75
  "MERGE_NOT_COMPLETED",
52
76
  "MERGE_COMPLETION_REFUSED",
77
+ // BAPI-1176
78
+ "SCOPE_CATCH_UP_REFUSED",
79
+ "SCOPE_CATCH_UP_UNAVAILABLE",
80
+ "POLICY_PATCH_INVALID",
81
+ "POLICY_PATH_NOT_PATCHABLE",
82
+ "POLICY_CHANGED_CONCURRENTLY",
83
+ "RUN_NOT_RESUMABLE",
84
+ "RUN_STOPPED",
85
+ "QUARANTINE_CLEARANCE_REQUIRED",
86
+ "RUN_CONTROL_CONFLICT",
53
87
  ];
54
88
  /** True when `code` is one of the closed repair refusal codes. */
55
89
  export function isOperatorRepairRefusalCode(code) {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Emits the spec re-review verdict (`spec_review.passed` /
5
5
  * `spec_review.changes_requested`) into the LOCAL conductor ledger, correlated
6
- * to the spec-review run via `BAPI_CONDUCTOR_RUN_ID`. The Epic Supervisor's
6
+ * to the spec-review run via `BAPI_EPIC_IMPLEMENTER_RUN_ID`. The Epic Supervisor's
7
7
  * reconcile pass folds these signals: `passed` → `reviewing → ready` (proceed to
8
8
  * implementation), `changes_requested` → `blocked` (escalate; do NOT implement).
9
9
  *
@@ -11,7 +11,7 @@
11
11
  * ledger is a per-worker SQLite file (`~/.config/bridge/events.db`). The
12
12
  * Heroku-hosted Bridge API backend has no access to it. So — exactly like the
13
13
  * PR-review producer ({@link ./pr-review-producer}) — the verdict is emitted by
14
- * the LOCAL spec-review run, whose environment carries `BAPI_CONDUCTOR_RUN_ID`
14
+ * the LOCAL spec-review run, whose environment carries `BAPI_EPIC_IMPLEMENTER_RUN_ID`
15
15
  * (injected at dispatch time by {@link ../review-tickets}.orchestrateReviewTickets
16
16
  * when an epic identity is present). This corrects the original BAPI-445 plan,
17
17
  * which proposed emitting from the backend review webhook
@@ -64,15 +64,15 @@ export function buildSpecReviewVerdictEventInput(ticketKey, verdict, reason, run
64
64
  }
65
65
  /**
66
66
  * Emit a spec re-review verdict for `ticketKey` into the local conductor ledger,
67
- * correlated to the spec-review run via `BAPI_CONDUCTOR_RUN_ID` (read from the
67
+ * correlated to the spec-review run via `BAPI_EPIC_IMPLEMENTER_RUN_ID` (read from the
68
68
  * injected env). Idempotent: deduped by `event_type` + `run_id`, so re-emitting
69
69
  * the same run's verdict is a no-op. A missing run id still emits (uncorrelated)
70
70
  * but is logged in the result so callers can detect the misconfiguration.
71
71
  */
72
72
  export async function emitSpecReviewVerdict(ticketKey, verdict, reason, deps = {}) {
73
73
  const emitIfNew = deps.emitIfNew ?? emitConductorEventIfNew;
74
- const run_id = deps.env?.BAPI_CONDUCTOR_RUN_ID?.trim() || null;
75
- const worker_id = deps.env?.BAPI_CONDUCTOR_WORKER_ID?.trim() || null;
74
+ const run_id = deps.env?.BAPI_EPIC_IMPLEMENTER_RUN_ID?.trim() || null;
75
+ const worker_id = deps.env?.BAPI_EPIC_IMPLEMENTER_WORKER_ID?.trim() || null;
76
76
  const eventType = verdict === "passed" ? SPEC_REVIEW_PASSED : SPEC_REVIEW_CHANGES_REQUESTED;
77
77
  const event = buildSpecReviewVerdictEventInput(ticketKey, verdict, reason, run_id, worker_id);
78
78
  const decision = await emitIfNew(event, {
@@ -83,6 +83,6 @@ export async function emitSpecReviewVerdict(ticketKey, verdict, reason, deps = {
83
83
  emitted: decision.emitted,
84
84
  event_type: eventType,
85
85
  run_id,
86
- reason: run_id ? reason : `${reason} (warning: no BAPI_CONDUCTOR_RUN_ID — verdict uncorrelated)`,
86
+ reason: run_id ? reason : `${reason} (warning: no BAPI_EPIC_IMPLEMENTER_RUN_ID — verdict uncorrelated)`,
87
87
  };
88
88
  }
@@ -191,16 +191,16 @@ function parseBoundedInt(raw, fallback, min, max) {
191
191
  }
192
192
  /**
193
193
  * Resolve store configuration from environment with safe defaults and bounds.
194
- * - `BAPI_CONDUCTOR_BUSY_TIMEOUT_MS` (default 10000, clamped [250, 120000]).
195
- * - `BAPI_CONDUCTOR_RETENTION_DAYS` (default 30). A value <= 0 DISABLES
194
+ * - `BAPI_EPIC_IMPLEMENTER_BUSY_TIMEOUT_MS` (default 10000, clamped [250, 120000]).
195
+ * - `BAPI_EPIC_IMPLEMENTER_RETENTION_DAYS` (default 30). A value <= 0 DISABLES
196
196
  * age-based retention; positive values are clamped to [1, 3650].
197
- * - `BAPI_CONDUCTOR_RETENTION_MAX_ROWS` (default 50000). A value <= 0 DISABLES
197
+ * - `BAPI_EPIC_IMPLEMENTER_RETENTION_MAX_ROWS` (default 50000). A value <= 0 DISABLES
198
198
  * the row cap; positive values are clamped to [100, 10000000].
199
199
  */
200
200
  export function resolveConductorStoreConfig(env = process.env) {
201
- const busy_timeout_ms = parseBoundedInt(env.BAPI_CONDUCTOR_BUSY_TIMEOUT_MS, BUSY_TIMEOUT_DEFAULT, BUSY_TIMEOUT_MIN, BUSY_TIMEOUT_MAX);
201
+ const busy_timeout_ms = parseBoundedInt(env.BAPI_EPIC_IMPLEMENTER_BUSY_TIMEOUT_MS, BUSY_TIMEOUT_DEFAULT, BUSY_TIMEOUT_MIN, BUSY_TIMEOUT_MAX);
202
202
  // Retention days: <= 0 disables (sentinel 0); positive values clamped.
203
- const rawDays = env.BAPI_CONDUCTOR_RETENTION_DAYS;
203
+ const rawDays = env.BAPI_EPIC_IMPLEMENTER_RETENTION_DAYS;
204
204
  let retention_days = RETENTION_DAYS_DEFAULT;
205
205
  if (rawDays !== undefined && rawDays.trim().length > 0) {
206
206
  const parsed = Number.parseInt(rawDays.trim(), 10);
@@ -215,7 +215,7 @@ export function resolveConductorStoreConfig(env = process.env) {
215
215
  }
216
216
  }
217
217
  // Retention max rows: <= 0 disables (sentinel 0); positive values clamped.
218
- const rawRows = env.BAPI_CONDUCTOR_RETENTION_MAX_ROWS;
218
+ const rawRows = env.BAPI_EPIC_IMPLEMENTER_RETENTION_MAX_ROWS;
219
219
  let retention_max_rows = RETENTION_MAX_ROWS_DEFAULT;
220
220
  if (rawRows !== undefined && rawRows.trim().length > 0) {
221
221
  const parsed = Number.parseInt(rawRows.trim(), 10);
@@ -230,7 +230,7 @@ export function resolveConductorStoreConfig(env = process.env) {
230
230
  }
231
231
  }
232
232
  // Message relay cooldown: <= 0 disables (sentinel 0); positive values clamped.
233
- const rawCooldown = env.BAPI_CONDUCTOR_MESSAGE_COOLDOWN_MS;
233
+ const rawCooldown = env.BAPI_EPIC_IMPLEMENTER_MESSAGE_COOLDOWN_MS;
234
234
  let message_cooldown_ms = MESSAGE_COOLDOWN_DEFAULT_MS;
235
235
  if (rawCooldown !== undefined && rawCooldown.trim().length > 0) {
236
236
  const parsed = Number.parseInt(rawCooldown.trim(), 10);
@@ -1211,7 +1211,7 @@ export async function sendWorkerMessage(input, config = resolveConductorStoreCon
1211
1211
  : config.message_cooldown_ms;
1212
1212
  const source = typeof input.source === "string" && input.source.trim().length > 0
1213
1213
  ? input.source.trim()
1214
- : "conductor-supervisor";
1214
+ : "epic-implementer-supervisor";
1215
1215
  const producer = typeof input.producer === "string" && input.producer.trim().length > 0
1216
1216
  ? input.producer.trim()
1217
1217
  : "worker-message-relay";
@@ -1334,7 +1334,7 @@ export async function checkWorkerMessages(input, config = resolveConductorStoreC
1334
1334
  continue;
1335
1335
  insertRelayAuditEvent(db, {
1336
1336
  id: `message.delivered:${row.id}`,
1337
- source: "conductor-worker",
1337
+ source: "epic-implementer-worker",
1338
1338
  type: "message.delivered",
1339
1339
  run_id: row.run_id,
1340
1340
  worker_id: row.worker_id,
@@ -1349,7 +1349,7 @@ export async function checkWorkerMessages(input, config = resolveConductorStoreC
1349
1349
  toAcked.run({ seq: row.seq });
1350
1350
  insertRelayAuditEvent(db, {
1351
1351
  id: `message.acked:${row.id}`,
1352
- source: "conductor-worker",
1352
+ source: "epic-implementer-worker",
1353
1353
  type: "message.acked",
1354
1354
  run_id: row.run_id,
1355
1355
  worker_id: row.worker_id,
@@ -65,13 +65,13 @@ const POLL_LIMIT_MAX = 1000;
65
65
  * tests/builds.
66
66
  */
67
67
  export function resolveSupervisorConfig(overrides = {}, env = process.env) {
68
- const wake_interval_ms = clampOverride(overrides.wake_interval_ms, parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_WAKE_INTERVAL_MS, WAKE_INTERVAL_DEFAULT_MS, WAKE_INTERVAL_MIN_MS, WAKE_INTERVAL_MAX_MS), WAKE_INTERVAL_MIN_MS, WAKE_INTERVAL_MAX_MS);
69
- const global_timeout_ms = clampOverride(overrides.global_timeout_ms, parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_GLOBAL_TIMEOUT_MS, GLOBAL_TIMEOUT_DEFAULT_MS, GLOBAL_TIMEOUT_MIN_MS, GLOBAL_TIMEOUT_MAX_MS), GLOBAL_TIMEOUT_MIN_MS, GLOBAL_TIMEOUT_MAX_MS);
70
- const escalation_cooldown_ms = clampOverride(overrides.escalation_cooldown_ms, parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_ESCALATION_COOLDOWN_MS, ESCALATION_COOLDOWN_DEFAULT_MS, ESCALATION_COOLDOWN_MIN_MS, ESCALATION_COOLDOWN_MAX_MS), ESCALATION_COOLDOWN_MIN_MS, ESCALATION_COOLDOWN_MAX_MS);
71
- const quiet_after_ms = parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_QUIET_AFTER_MS, QUIET_AFTER_DEFAULT_MS, QUIET_AFTER_MIN_MS, QUIET_AFTER_MAX_MS);
72
- const liveness_stalled_after_ms = parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_LIVENESS_STALLED_AFTER_MS, LIVENESS_STALLED_AFTER_DEFAULT_MS, LIVENESS_STALLED_AFTER_MIN_MS, LIVENESS_STALLED_AFTER_MAX_MS);
73
- const dead_after_ms = parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_DEAD_AFTER_MS, DEAD_AFTER_DEFAULT_MS, DEAD_AFTER_MIN_MS, DEAD_AFTER_MAX_MS);
74
- const poll_limit = parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_SUPERVISOR_POLL_LIMIT, POLL_LIMIT_DEFAULT, POLL_LIMIT_MIN, POLL_LIMIT_MAX);
68
+ const wake_interval_ms = clampOverride(overrides.wake_interval_ms, parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_WAKE_INTERVAL_MS, WAKE_INTERVAL_DEFAULT_MS, WAKE_INTERVAL_MIN_MS, WAKE_INTERVAL_MAX_MS), WAKE_INTERVAL_MIN_MS, WAKE_INTERVAL_MAX_MS);
69
+ const global_timeout_ms = clampOverride(overrides.global_timeout_ms, parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_GLOBAL_TIMEOUT_MS, GLOBAL_TIMEOUT_DEFAULT_MS, GLOBAL_TIMEOUT_MIN_MS, GLOBAL_TIMEOUT_MAX_MS), GLOBAL_TIMEOUT_MIN_MS, GLOBAL_TIMEOUT_MAX_MS);
70
+ const escalation_cooldown_ms = clampOverride(overrides.escalation_cooldown_ms, parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_ESCALATION_COOLDOWN_MS, ESCALATION_COOLDOWN_DEFAULT_MS, ESCALATION_COOLDOWN_MIN_MS, ESCALATION_COOLDOWN_MAX_MS), ESCALATION_COOLDOWN_MIN_MS, ESCALATION_COOLDOWN_MAX_MS);
71
+ const quiet_after_ms = parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_QUIET_AFTER_MS, QUIET_AFTER_DEFAULT_MS, QUIET_AFTER_MIN_MS, QUIET_AFTER_MAX_MS);
72
+ const liveness_stalled_after_ms = parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_LIVENESS_STALLED_AFTER_MS, LIVENESS_STALLED_AFTER_DEFAULT_MS, LIVENESS_STALLED_AFTER_MIN_MS, LIVENESS_STALLED_AFTER_MAX_MS);
73
+ const dead_after_ms = parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_DEAD_AFTER_MS, DEAD_AFTER_DEFAULT_MS, DEAD_AFTER_MIN_MS, DEAD_AFTER_MAX_MS);
74
+ const poll_limit = parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_SUPERVISOR_POLL_LIMIT, POLL_LIMIT_DEFAULT, POLL_LIMIT_MIN, POLL_LIMIT_MAX);
75
75
  return {
76
76
  wake_interval_ms,
77
77
  global_timeout_ms,
@@ -95,19 +95,19 @@ function clampOverride(override, base, min, max) {
95
95
  function resolveStallThresholds(env) {
96
96
  return {
97
97
  // A worker that never starts should surface quickly.
98
- not_started: parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_STALL_NOT_STARTED_MS, 15 * MINUTE_MS, MINUTE_MS, HOUR_MS),
98
+ not_started: parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_STALL_NOT_STARTED_MS, 15 * MINUTE_MS, MINUTE_MS, HOUR_MS),
99
99
  // Active work (tests/builds) is given a long, tolerant budget.
100
- active: parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_STALL_ACTIVE_MS, 2 * HOUR_MS, 10 * MINUTE_MS, 12 * HOUR_MS),
100
+ active: parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_STALL_ACTIVE_MS, 2 * HOUR_MS, 10 * MINUTE_MS, 12 * HOUR_MS),
101
101
  // Already-stalled workers re-escalate on a slower cadence.
102
- stalled: parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_STALL_STALLED_MS, 30 * MINUTE_MS, 5 * MINUTE_MS, 6 * HOUR_MS),
102
+ stalled: parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_STALL_STALLED_MS, 30 * MINUTE_MS, 5 * MINUTE_MS, 6 * HOUR_MS),
103
103
  // Blocked workers may legitimately wait a long time (human input, deps).
104
- blocked: parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_STALL_BLOCKED_MS, 24 * HOUR_MS, 30 * MINUTE_MS, 7 * 24 * HOUR_MS),
104
+ blocked: parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_STALL_BLOCKED_MS, 24 * HOUR_MS, 30 * MINUTE_MS, 7 * 24 * HOUR_MS),
105
105
  // A done candidate that never gets verified should surface.
106
- candidate_done: parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_STALL_CANDIDATE_DONE_MS, 30 * MINUTE_MS, 5 * MINUTE_MS, 6 * HOUR_MS),
106
+ candidate_done: parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_STALL_CANDIDATE_DONE_MS, 30 * MINUTE_MS, 5 * MINUTE_MS, 6 * HOUR_MS),
107
107
  // Verification (CI) gets a long, tolerant budget like active work.
108
- verifying: parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_STALL_VERIFYING_MS, 2 * HOUR_MS, 10 * MINUTE_MS, 12 * HOUR_MS),
108
+ verifying: parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_STALL_VERIFYING_MS, 2 * HOUR_MS, 10 * MINUTE_MS, 12 * HOUR_MS),
109
109
  // Unknown-state workers surface on a moderate cadence.
110
- unknown: parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_STALL_UNKNOWN_MS, 30 * MINUTE_MS, 5 * MINUTE_MS, 6 * HOUR_MS),
110
+ unknown: parseBoundedSupervisorInt(env.BAPI_EPIC_IMPLEMENTER_STALL_UNKNOWN_MS, 30 * MINUTE_MS, 5 * MINUTE_MS, 6 * HOUR_MS),
111
111
  // Terminal states never "stall"; large sentinels keep arithmetic uniform.
112
112
  complete: Number.MAX_SAFE_INTEGER,
113
113
  failed: Number.MAX_SAFE_INTEGER,
@@ -86,11 +86,11 @@ export async function emitSupervisorAssessmentIfNew(input, deps = {}) {
86
86
  };
87
87
  const event = {
88
88
  id: eventId,
89
- source: "conductor-supervisor",
89
+ source: "epic-implementer-supervisor",
90
90
  type: "supervisor.assessment",
91
91
  run_id: input.run_id,
92
92
  worker_id: input.idempotency.worker_id ?? input.worker_id ?? null,
93
- producer: "conductor-supervisor",
93
+ producer: "epic-implementer-supervisor",
94
94
  observed_via: "supervisor",
95
95
  data: {
96
96
  summary: `supervisor assessment: ${input.idempotency.reason}`,
@@ -40,7 +40,7 @@ export function buildSupervisorEscalationWorkerMessage(candidate, assessment, st
40
40
  status: "escalated",
41
41
  details,
42
42
  },
43
- source: "conductor-supervisor",
43
+ source: "epic-implementer-supervisor",
44
44
  producer: "worker-message-relay",
45
45
  };
46
46
  }
@@ -71,7 +71,7 @@ export function buildSupervisorRemediationWorkerMessage(input) {
71
71
  truncated: input.truncated,
72
72
  },
73
73
  },
74
- source: "conductor-supervisor",
74
+ source: "epic-implementer-supervisor",
75
75
  producer: "worker-message-relay",
76
76
  };
77
77
  }
@@ -6,12 +6,12 @@
6
6
  * Every worker-facing ledger operation (`check_messages`, and the
7
7
  * `wait_for_done_gate` event emit) therefore runs through this module, which
8
8
  * shells out to the packaged `conductor-bin.js` CLI executed by the conductor
9
- * Node runtime captured at dispatch time in `CONDUCTOR_NODE_PATH`. Conductor-OWNED
9
+ * Node runtime captured at dispatch time in `EPIC_IMPLEMENTER_NODE_PATH`. Conductor-OWNED
10
10
  * processes (the CLI itself, the supervisor/epic runtimes) keep calling
11
11
  * `store.ts` directly — they legitimately own the native load.
12
12
  *
13
- * Hard-fail contract: when `CONDUCTOR_NODE_PATH` is missing/invalid or
14
- * `BAPI_CONDUCTOR_CLI_FILE` is absent, this boundary throws a typed
13
+ * Hard-fail contract: when `EPIC_IMPLEMENTER_NODE_PATH` is missing/invalid or
14
+ * `BAPI_EPIC_IMPLEMENTER_CLI_FILE` is absent, this boundary throws a typed
15
15
  * {@link ConductorLedgerSubprocessRuntimeError} rather than silently falling back
16
16
  * to the worker's own `process.execPath`. Falling back would re-introduce the
17
17
  * native load into the worker Node — the exact failure mode this slice removes.
@@ -33,9 +33,9 @@ import { isAbsolute as pathIsAbsolute } from "node:path";
33
33
  import { ConductorLedgerSubprocessRuntimeError, ConductorWorkerContextRequiredError, } from "./errors.js";
34
34
  import { makeProducerDedupeKey, makeStableProducerEventId, } from "./producer-ledger.js";
35
35
  /** The env key that carries the captured conductor Node executable path. */
36
- export const CONDUCTOR_NODE_PATH_ENV = "CONDUCTOR_NODE_PATH";
36
+ export const EPIC_IMPLEMENTER_NODE_PATH_ENV = "EPIC_IMPLEMENTER_NODE_PATH";
37
37
  /** The (pre-existing) env key that carries the packaged `conductor-bin.js` path. */
38
- export const BAPI_CONDUCTOR_CLI_FILE_ENV = "BAPI_CONDUCTOR_CLI_FILE";
38
+ export const BAPI_EPIC_IMPLEMENTER_CLI_FILE_ENV = "BAPI_EPIC_IMPLEMENTER_CLI_FILE";
39
39
  /**
40
40
  * Bounded stdout buffer for a conductor CLI response. Conductor JSON responses
41
41
  * (message batches / emit summaries) are small; 8 MiB is generous headroom while
@@ -60,9 +60,9 @@ function nonEmpty(value) {
60
60
  /**
61
61
  * Validate and resolve the worker-ledger subprocess runtime from the environment.
62
62
  *
63
- * `CONDUCTOR_NODE_PATH` must be set, non-empty, and an ABSOLUTE path (a relative
63
+ * `EPIC_IMPLEMENTER_NODE_PATH` must be set, non-empty, and an ABSOLUTE path (a relative
64
64
  * value like `node` / `./node` is rejected as `invalid` — it must never resolve
65
- * against `PATH` or the worker cwd). `BAPI_CONDUCTOR_CLI_FILE` must be set and
65
+ * against `PATH` or the worker cwd). `BAPI_EPIC_IMPLEMENTER_CLI_FILE` must be set and
66
66
  * non-empty. On any failure this throws a typed
67
67
  * {@link ConductorLedgerSubprocessRuntimeError}; it NEVER falls back to
68
68
  * `process.execPath`.
@@ -83,24 +83,24 @@ export function resolveWorkerLedgerCliRuntime(deps = {}) {
83
83
  const isAbsolute = deps.isAbsolute ?? pathIsAbsolute;
84
84
  // `undefined` specifically — an env var SET to "" is present-but-invalid and
85
85
  // falls through to the fail-loud path below.
86
- if (env[CONDUCTOR_NODE_PATH_ENV] === undefined &&
87
- env[BAPI_CONDUCTOR_CLI_FILE_ENV] === undefined) {
86
+ if (env[EPIC_IMPLEMENTER_NODE_PATH_ENV] === undefined &&
87
+ env[BAPI_EPIC_IMPLEMENTER_CLI_FILE_ENV] === undefined) {
88
88
  throw new ConductorWorkerContextRequiredError([
89
- CONDUCTOR_NODE_PATH_ENV,
90
- BAPI_CONDUCTOR_CLI_FILE_ENV,
89
+ EPIC_IMPLEMENTER_NODE_PATH_ENV,
90
+ BAPI_EPIC_IMPLEMENTER_CLI_FILE_ENV,
91
91
  ]);
92
92
  }
93
- const nodePathRaw = env[CONDUCTOR_NODE_PATH_ENV];
93
+ const nodePathRaw = env[EPIC_IMPLEMENTER_NODE_PATH_ENV];
94
94
  if (!nonEmpty(nodePathRaw)) {
95
- throw new ConductorLedgerSubprocessRuntimeError("missing", CONDUCTOR_NODE_PATH_ENV);
95
+ throw new ConductorLedgerSubprocessRuntimeError("missing", EPIC_IMPLEMENTER_NODE_PATH_ENV);
96
96
  }
97
97
  const nodePath = nodePathRaw.trim();
98
98
  if (!isAbsolute(nodePath)) {
99
- throw new ConductorLedgerSubprocessRuntimeError("invalid", CONDUCTOR_NODE_PATH_ENV);
99
+ throw new ConductorLedgerSubprocessRuntimeError("invalid", EPIC_IMPLEMENTER_NODE_PATH_ENV);
100
100
  }
101
- const cliFileRaw = env[BAPI_CONDUCTOR_CLI_FILE_ENV];
101
+ const cliFileRaw = env[BAPI_EPIC_IMPLEMENTER_CLI_FILE_ENV];
102
102
  if (!nonEmpty(cliFileRaw)) {
103
- throw new ConductorLedgerSubprocessRuntimeError("cli_missing", BAPI_CONDUCTOR_CLI_FILE_ENV);
103
+ throw new ConductorLedgerSubprocessRuntimeError("cli_missing", BAPI_EPIC_IMPLEMENTER_CLI_FILE_ENV);
104
104
  }
105
105
  return { nodePath, cliFile: cliFileRaw.trim() };
106
106
  }
@@ -129,14 +129,14 @@ export function execConductorCli(subcommandArgs, stdin, deps = {}) {
129
129
  if (error) {
130
130
  // A non-zero exit, spawn failure, or timeout kill — never surface the
131
131
  // raw error text (it can carry paths/secrets). Fixed reason only.
132
- reject(new ConductorLedgerSubprocessRuntimeError("spawn_failed", CONDUCTOR_NODE_PATH_ENV));
132
+ reject(new ConductorLedgerSubprocessRuntimeError("spawn_failed", EPIC_IMPLEMENTER_NODE_PATH_ENV));
133
133
  return;
134
134
  }
135
135
  resolve(stdout);
136
136
  });
137
137
  }
138
138
  catch {
139
- reject(new ConductorLedgerSubprocessRuntimeError("spawn_failed", CONDUCTOR_NODE_PATH_ENV));
139
+ reject(new ConductorLedgerSubprocessRuntimeError("spawn_failed", EPIC_IMPLEMENTER_NODE_PATH_ENV));
140
140
  return;
141
141
  }
142
142
  if (stdin !== undefined) {
@@ -169,7 +169,7 @@ export function parseCliJsonStdout(stdout) {
169
169
  return JSON.parse(stdout);
170
170
  }
171
171
  catch {
172
- throw new ConductorLedgerSubprocessRuntimeError("malformed_stdout", CONDUCTOR_NODE_PATH_ENV);
172
+ throw new ConductorLedgerSubprocessRuntimeError("malformed_stdout", EPIC_IMPLEMENTER_NODE_PATH_ENV);
173
173
  }
174
174
  }
175
175
  /**
@@ -29,7 +29,7 @@ const EPIC_SLUG_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
29
29
  const PLACEHOLDER_RE = /TBD-\d+/;
30
30
  /**
31
31
  * The conductor's hard spec bound, mirroring `_SPEC_REVIEW_TICKET_SPEC_MAX_CHARS`
32
- * (64,000) in `api/library/epic_conductor/reconciler.py`. Since PR #848 the
32
+ * (64,000) in `api/library/epic_implementer/reconciler.py`. Since PR #848 the
33
33
  * reconciler marks overflow beyond its own bound visibly (no silent loss), so
34
34
  * this authoring guard exists only to reject a spec the conductor itself could
35
35
  * not accept. For Jira-backed tickets the practical limit binds far earlier:
@@ -576,7 +576,7 @@ function findCycle(keys, edgeKeys) {
576
576
  */
577
577
  const PY_WHITESPACE = /[\t\n\v\f\r \x1c-\x1f\x85\xa0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+/g;
578
578
  /**
579
- * Mirror `normalize_ticket_spec()` (`api/library/epic_conductor/spec_hash.py`)
579
+ * Mirror `normalize_ticket_spec()` (`api/library/epic_implementer/spec_hash.py`)
580
580
  * so a cosmetic diff never triggers a Jira rewrite.
581
581
  *
582
582
  * Because whitespace collapses, a pure reflow/indent edit is deliberately NOT a
@@ -0,0 +1,72 @@
1
+ /** Human-readable progress for headless Cursor runs without exposing tool payloads. */
2
+ import { spawn } from "node:child_process";
3
+ import { createInterface } from "node:readline";
4
+ import { fileURLToPath } from "node:url";
5
+ import path from "node:path";
6
+ /** Only render assistant prose and tool NAMES; tool arguments/results may contain secrets. */
7
+ export function formatCursorProgressEvent(event) {
8
+ if (event.type === "system" && event.subtype === "init") {
9
+ return "Cursor agent started. Progress will appear below.\n";
10
+ }
11
+ if (event.type === "assistant") {
12
+ const content = event.message?.content;
13
+ const prose = (Array.isArray(content) ? content : [])
14
+ .filter((item) => item && item.type === "text" && typeof item.text === "string")
15
+ .map((item) => item.text)
16
+ .join("");
17
+ return prose ? `${prose}\n` : null;
18
+ }
19
+ if (event.type === "tool_call" && event.subtype === "started") {
20
+ const call = event.tool_call;
21
+ const name = call && typeof call === "object" && !Array.isArray(call)
22
+ ? Object.keys(call)[0]
23
+ : undefined;
24
+ if (!name)
25
+ return "Working with a tool...\n";
26
+ const label = name.replace(/ToolCall$/, "").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
27
+ return `Using ${label}...\n`;
28
+ }
29
+ return null;
30
+ }
31
+ async function main(argv) {
32
+ const args = argv[0] === "--" ? argv.slice(1) : argv;
33
+ if (args[0] !== "cursor-agent") {
34
+ process.stderr.write("cursor-agent-progress expects cursor-agent as its child command.\n");
35
+ return 2;
36
+ }
37
+ const child = spawn(args[0], args.slice(1), { stdio: ["inherit", "pipe", "inherit"] });
38
+ const forwardSignal = (signal) => child.kill(signal);
39
+ process.on("SIGINT", forwardSignal);
40
+ process.on("SIGTERM", forwardSignal);
41
+ const completion = new Promise((resolve) => {
42
+ child.once("close", (code, signal) => resolve(code ?? (signal ? 128 : 1)));
43
+ child.once("error", () => resolve(1));
44
+ });
45
+ const lines = createInterface({ input: child.stdout });
46
+ let sawAssistantText = false;
47
+ for await (const line of lines) {
48
+ let event;
49
+ try {
50
+ event = JSON.parse(line);
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ const rendered = formatCursorProgressEvent(event);
56
+ if (rendered) {
57
+ if (event.type === "assistant")
58
+ sawAssistantText = true;
59
+ process.stdout.write(rendered);
60
+ }
61
+ if (event.type === "result" && !sawAssistantText && typeof event.result === "string") {
62
+ process.stdout.write(`${event.result}\n`);
63
+ }
64
+ }
65
+ const exit = await completion;
66
+ process.off("SIGINT", forwardSignal);
67
+ process.off("SIGTERM", forwardSignal);
68
+ return exit;
69
+ }
70
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
71
+ void main(process.argv.slice(2)).then((code) => { process.exitCode = code; });
72
+ }