@bridge_gpt/mcp-server 0.2.16 → 0.2.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONDUCTOR.md +75 -0
- package/README.md +2 -2
- package/build/agent-capabilities/probe-context.js +13 -3
- package/build/agent-capabilities/probes.js +262 -11
- package/build/agent-capabilities/reporter.js +1 -0
- package/build/agents.generated.js +3 -3
- package/build/backend-warnings.js +44 -0
- package/build/claude-settings.js +129 -0
- package/build/commands.generated.js +7 -6
- package/build/conductor/bridge-api-client.js +198 -18
- package/build/conductor/claude-hook.js +22 -4
- package/build/conductor/cli.js +76 -25
- package/build/conductor/deny-enforcement-preflight.js +96 -0
- package/build/conductor/doctor.js +183 -2
- package/build/conductor/done-gate.js +5 -0
- package/build/conductor/epic-reconcile.js +71 -14
- package/build/conductor/epic-runtime.js +839 -67
- package/build/conductor/epic-state.js +524 -63
- package/build/conductor/errors.js +156 -3
- package/build/conductor/event-accessors.js +252 -0
- package/build/conductor/file-scope-guard.js +201 -0
- package/build/conductor/github-mergeability.js +85 -0
- package/build/conductor/local-merge.js +47 -1
- package/build/conductor/merge-identity.js +41 -0
- package/build/conductor/merge-ledger.js +19 -72
- package/build/conductor/plan.js +12 -2
- package/build/conductor/pr-ci-producer.js +17 -2
- package/build/conductor/pr-discovery.js +11 -1
- package/build/conductor/producer-ledger.js +1 -1
- package/build/conductor/store.js +161 -18
- package/build/conductor/supervisor-config.js +4 -39
- package/build/conductor/supervisor-escalation.js +10 -26
- package/build/conductor/supervisor-ledger.js +5 -12
- package/build/conductor/supervisor-merge.js +32 -5
- package/build/conductor/supervisor-message-relay.js +2 -5
- package/build/conductor/supervisor-notification.js +1 -1
- package/build/conductor/supervisor-runtime.js +12 -54
- package/build/conductor/supervisor-state.js +4 -18
- package/build/conductor/supervisor-types.js +2 -2
- package/build/conductor/taxonomy.js +12 -0
- package/build/conductor/tools.js +28 -6
- package/build/conductor/worker-ledger-cli.js +244 -0
- package/build/conductor-bin.js +1800 -5166
- package/build/conductor-claude-hook-bin.js +4 -2
- package/build/doctor.js +40 -0
- package/build/executor/cli.js +229 -0
- package/build/executor/credentials.js +65 -0
- package/build/executor/deps.js +117 -0
- package/build/executor/env.js +79 -0
- package/build/executor/heartbeat.js +59 -0
- package/build/executor/http-client.js +131 -0
- package/build/executor/index.js +10 -0
- package/build/executor/job-errors.js +55 -0
- package/build/executor/job-log-registry.js +110 -0
- package/build/executor/job-runner.js +688 -0
- package/build/executor/job-types.js +60 -0
- package/build/executor/merge-job.js +155 -0
- package/build/executor/observation.js +123 -0
- package/build/executor/permissions.js +79 -0
- package/build/executor/preflight.js +144 -0
- package/build/executor/process.js +81 -0
- package/build/executor/prompt-spec.js +235 -0
- package/build/executor/results.js +134 -0
- package/build/executor/resume-pre-spawn.js +179 -0
- package/build/executor/runner.js +98 -0
- package/build/executor/terminal-mutation.js +34 -0
- package/build/executor/test-clock.js +109 -0
- package/build/executor/types.js +18 -0
- package/build/executor/verdict-artifact.js +53 -0
- package/build/executor/viewer-tabs.js +78 -0
- package/build/executor/watch-cli.js +113 -0
- package/build/executor/worker-command.js +106 -0
- package/build/executor/worker-finalization.js +97 -0
- package/build/executor/worker-log.js +92 -0
- package/build/executor/worktree-gc.js +134 -0
- package/build/executor/worktree-inspection.js +86 -0
- package/build/executor/worktree.js +103 -0
- package/build/index.js +13950 -9669
- package/build/install-bridge.js +25 -8
- package/build/install-doctor.js +387 -0
- package/build/mcp-invoke.js +19 -3
- package/build/mcp-provisioning.js +31 -25
- package/build/mcp-registration-doctor.js +27 -7
- package/build/mcp-server-invocation.js +152 -0
- package/build/pipelines.generated.js +31 -6
- package/build/readme.generated.js +1 -1
- package/build/regression-check.js +53 -1
- package/build/review-tickets.js +175 -21
- package/build/sfcc/reads-site-preference.js +52 -19
- package/build/start-tickets-conductor.js +47 -99
- package/build/start-tickets-prereqs.js +185 -4
- package/build/start-tickets.js +218 -180
- package/build/version.generated.js +1 -1
- package/build/visual-diff-worker.js +313 -0
- package/build/visual-diff.js +632 -0
- package/build/worktree-core.js +202 -0
- package/package.json +10 -6
- package/pipelines/review-ticket.json +24 -2
- package/public/css/main.min.css +3311 -1
- package/public/css/main.min.css.map +1 -1
- package/public/js/main.min.js +7924 -1
- package/public/js/main.min.js.map +1 -1
- package/smoke-test/SMOKE-TEST.md +5 -2
|
@@ -2,15 +2,10 @@
|
|
|
2
2
|
* Supervisor configuration resolution (BAPI-396, conductor C4).
|
|
3
3
|
*
|
|
4
4
|
* Centralizes the supervisor's wake cadence, global timeout, per-state stall
|
|
5
|
-
* thresholds, liveness thresholds, escalation cooldown
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Token discipline: the WAKE cadence (frequent, deterministic event polling) is
|
|
11
|
-
* intentionally separate from the LLM cadence (judgment-triggered only). The
|
|
12
|
-
* deterministic loop runs every `wake_interval_ms`; the LLM is consulted at most
|
|
13
|
-
* `llm_max_calls` times per run and only for ambiguous escalation candidates.
|
|
5
|
+
* thresholds, liveness thresholds, and escalation cooldown. All values are
|
|
6
|
+
* resolved from `process.env` with conservative, bounded defaults following the
|
|
7
|
+
* style of {@link resolveConductorStoreConfig} in `store.ts`: malformed values
|
|
8
|
+
* fall back to the default and excessive values are clamped.
|
|
14
9
|
*/
|
|
15
10
|
const MINUTE_MS = 60_000;
|
|
16
11
|
const HOUR_MS = 60 * MINUTE_MS;
|
|
@@ -36,13 +31,6 @@ const LIVENESS_STALLED_AFTER_MAX_MS = 4 * HOUR_MS;
|
|
|
36
31
|
const DEAD_AFTER_DEFAULT_MS = 2 * HOUR_MS;
|
|
37
32
|
const DEAD_AFTER_MIN_MS = 10 * MINUTE_MS;
|
|
38
33
|
const DEAD_AFTER_MAX_MS = 24 * HOUR_MS;
|
|
39
|
-
// --- LLM budget. ----------------------------------------------------------
|
|
40
|
-
const LLM_MAX_CALLS_DEFAULT = 10;
|
|
41
|
-
const LLM_MAX_CALLS_MIN = 0;
|
|
42
|
-
const LLM_MAX_CALLS_MAX = 1000;
|
|
43
|
-
const LLM_TIMEOUT_DEFAULT_MS = 30_000;
|
|
44
|
-
const LLM_TIMEOUT_MIN_MS = 1_000;
|
|
45
|
-
const LLM_TIMEOUT_MAX_MS = 120_000;
|
|
46
34
|
/**
|
|
47
35
|
* Parse a bounded integer from an env-style string. Mirrors `parseBoundedInt`
|
|
48
36
|
* in `store.ts`: blank/undefined/non-finite/float-with-fraction/non-numeric
|
|
@@ -68,19 +56,6 @@ export function parseBoundedSupervisorInt(raw, fallback, min, max) {
|
|
|
68
56
|
const POLL_LIMIT_DEFAULT = 200;
|
|
69
57
|
const POLL_LIMIT_MIN = 1;
|
|
70
58
|
const POLL_LIMIT_MAX = 1000;
|
|
71
|
-
/** Truthy parse for boolean-ish env strings (`0`/`false`/`off`/`no` => false). */
|
|
72
|
-
function parseBoolEnv(raw, fallback) {
|
|
73
|
-
if (raw === undefined)
|
|
74
|
-
return fallback;
|
|
75
|
-
const v = raw.trim().toLowerCase();
|
|
76
|
-
if (v.length === 0)
|
|
77
|
-
return fallback;
|
|
78
|
-
if (v === "0" || v === "false" || v === "off" || v === "no")
|
|
79
|
-
return false;
|
|
80
|
-
if (v === "1" || v === "true" || v === "on" || v === "yes")
|
|
81
|
-
return true;
|
|
82
|
-
return fallback;
|
|
83
|
-
}
|
|
84
59
|
/**
|
|
85
60
|
* Resolve the supervisor configuration. Precedence per field: a direct override
|
|
86
61
|
* (when provided and valid) wins over the environment, which wins over the
|
|
@@ -96,13 +71,6 @@ export function resolveSupervisorConfig(overrides = {}, env = process.env) {
|
|
|
96
71
|
const quiet_after_ms = parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_QUIET_AFTER_MS, QUIET_AFTER_DEFAULT_MS, QUIET_AFTER_MIN_MS, QUIET_AFTER_MAX_MS);
|
|
97
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);
|
|
98
73
|
const dead_after_ms = parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_DEAD_AFTER_MS, DEAD_AFTER_DEFAULT_MS, DEAD_AFTER_MIN_MS, DEAD_AFTER_MAX_MS);
|
|
99
|
-
// LLM is enabled by default but degrades cleanly. A direct `llm_enabled:false`
|
|
100
|
-
// override (e.g. CLI `--no-llm`) forces deterministic-only mode.
|
|
101
|
-
const llm_enabled = overrides.llm_enabled !== undefined
|
|
102
|
-
? overrides.llm_enabled
|
|
103
|
-
: parseBoolEnv(env.BAPI_CONDUCTOR_LLM_ENABLED, true);
|
|
104
|
-
const llm_max_calls = clampOverride(overrides.llm_max_calls, parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_LLM_MAX_CALLS, LLM_MAX_CALLS_DEFAULT, LLM_MAX_CALLS_MIN, LLM_MAX_CALLS_MAX), LLM_MAX_CALLS_MIN, LLM_MAX_CALLS_MAX);
|
|
105
|
-
const llm_timeout_ms = clampOverride(overrides.llm_timeout_ms, parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_LLM_TIMEOUT_MS, LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS), LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS);
|
|
106
74
|
const poll_limit = parseBoundedSupervisorInt(env.BAPI_CONDUCTOR_SUPERVISOR_POLL_LIMIT, POLL_LIMIT_DEFAULT, POLL_LIMIT_MIN, POLL_LIMIT_MAX);
|
|
107
75
|
return {
|
|
108
76
|
wake_interval_ms,
|
|
@@ -114,9 +82,6 @@ export function resolveSupervisorConfig(overrides = {}, env = process.env) {
|
|
|
114
82
|
dead_after_ms,
|
|
115
83
|
},
|
|
116
84
|
escalation_cooldown_ms,
|
|
117
|
-
llm_enabled,
|
|
118
|
-
llm_max_calls,
|
|
119
|
-
llm_timeout_ms,
|
|
120
85
|
poll_limit,
|
|
121
86
|
};
|
|
122
87
|
}
|
|
@@ -34,8 +34,7 @@ function elapsedSinceSignal(worker, now) {
|
|
|
34
34
|
* Inspect run state and return deterministic escalation candidates. Reasons:
|
|
35
35
|
* `worker_not_started`, `worker_stalled`, `worker_blocked`, `worker_dead`,
|
|
36
36
|
* `candidate_done_stuck`, `verification_stalled`, and `global_timeout`. Terminal
|
|
37
|
-
* workers never produce candidates.
|
|
38
|
-
* from an LLM judgment (long-running stalls) vs. unambiguous ones.
|
|
37
|
+
* workers never produce candidates.
|
|
39
38
|
*/
|
|
40
39
|
export function findSupervisorEscalationCandidates(state, config, now) {
|
|
41
40
|
const candidates = [];
|
|
@@ -59,7 +58,6 @@ export function findSupervisorEscalationCandidates(state, config, now) {
|
|
|
59
58
|
state: worker.state,
|
|
60
59
|
liveness: worker.liveness,
|
|
61
60
|
elapsed_ms: elapsed,
|
|
62
|
-
ambiguous: false,
|
|
63
61
|
context: baseContext,
|
|
64
62
|
});
|
|
65
63
|
continue;
|
|
@@ -82,7 +80,6 @@ export function findSupervisorEscalationCandidates(state, config, now) {
|
|
|
82
80
|
state: worker.state,
|
|
83
81
|
liveness: worker.liveness,
|
|
84
82
|
elapsed_ms: elapsed,
|
|
85
|
-
ambiguous: false,
|
|
86
83
|
context: baseContext,
|
|
87
84
|
});
|
|
88
85
|
}
|
|
@@ -95,7 +92,6 @@ export function findSupervisorEscalationCandidates(state, config, now) {
|
|
|
95
92
|
state: worker.state,
|
|
96
93
|
liveness: worker.liveness,
|
|
97
94
|
elapsed_ms: elapsed,
|
|
98
|
-
ambiguous: false,
|
|
99
95
|
context: { ...baseContext, blocked_reason: worker.blocked_reason },
|
|
100
96
|
});
|
|
101
97
|
break;
|
|
@@ -107,7 +103,6 @@ export function findSupervisorEscalationCandidates(state, config, now) {
|
|
|
107
103
|
state: worker.state,
|
|
108
104
|
liveness: worker.liveness,
|
|
109
105
|
elapsed_ms: elapsed,
|
|
110
|
-
ambiguous: true,
|
|
111
106
|
context: baseContext,
|
|
112
107
|
});
|
|
113
108
|
break;
|
|
@@ -124,7 +119,6 @@ export function findSupervisorEscalationCandidates(state, config, now) {
|
|
|
124
119
|
state: worker.state,
|
|
125
120
|
liveness: worker.liveness,
|
|
126
121
|
elapsed_ms: elapsed,
|
|
127
|
-
ambiguous: true,
|
|
128
122
|
context: baseContext,
|
|
129
123
|
});
|
|
130
124
|
}
|
|
@@ -141,7 +135,6 @@ export function findSupervisorEscalationCandidates(state, config, now) {
|
|
|
141
135
|
state: worker.state,
|
|
142
136
|
liveness: worker.liveness,
|
|
143
137
|
elapsed_ms: elapsed,
|
|
144
|
-
ambiguous: true,
|
|
145
138
|
context: baseContext,
|
|
146
139
|
});
|
|
147
140
|
}
|
|
@@ -160,7 +153,6 @@ export function findSupervisorEscalationCandidates(state, config, now) {
|
|
|
160
153
|
state: null,
|
|
161
154
|
liveness: null,
|
|
162
155
|
elapsed_ms: Math.max(0, now - deadlineMs),
|
|
163
|
-
ambiguous: false,
|
|
164
156
|
context: {
|
|
165
157
|
run_id: state.run_id,
|
|
166
158
|
deadline_at: state.global_deadline_at,
|
|
@@ -176,23 +168,20 @@ function cooldownWindowFor(now, cooldownMs) {
|
|
|
176
168
|
return String(Math.floor(now / width));
|
|
177
169
|
}
|
|
178
170
|
/**
|
|
179
|
-
* Decide whether a candidate should be emitted now. A candidate is
|
|
171
|
+
* Decide whether a candidate should be emitted now. A candidate is a DUPLICATE
|
|
180
172
|
* when an escalation for the same worker + reason was already emitted (or hit a
|
|
181
173
|
* duplicate) within the SAME cooldown window. Returns the cooldown window
|
|
182
174
|
* identifier the caller must pass to `makeSupervisorIdempotencyKey` so the
|
|
183
|
-
* idempotency key and the
|
|
175
|
+
* idempotency key and the duplicate check agree.
|
|
184
176
|
*/
|
|
185
177
|
export function shouldEmitEscalation(state, candidate, config, now) {
|
|
186
178
|
const cooldownWindow = cooldownWindowFor(now, config.escalation_cooldown_ms);
|
|
187
179
|
const alreadyDecided = state.escalations.some((record) => record.reason === candidate.reason &&
|
|
188
180
|
(record.worker_id ?? null) === (candidate.worker_id ?? null) &&
|
|
189
181
|
record.cooldown_window === cooldownWindow &&
|
|
190
|
-
// A prior
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
(record.outcome === "emitted" ||
|
|
194
|
-
record.outcome === "duplicate" ||
|
|
195
|
-
record.outcome === "suppressed"));
|
|
182
|
+
// A prior emitted/duplicate decision in this window is binding — do not
|
|
183
|
+
// re-decide until the window rolls over.
|
|
184
|
+
(record.outcome === "emitted" || record.outcome === "duplicate"));
|
|
196
185
|
return { emit: !alreadyDecided, cooldown_window: cooldownWindow };
|
|
197
186
|
}
|
|
198
187
|
/**
|
|
@@ -227,18 +216,13 @@ function formatElapsed(ms) {
|
|
|
227
216
|
}
|
|
228
217
|
/**
|
|
229
218
|
* Render a concise, SECRET-FREE terminal escalation line. Includes run id,
|
|
230
|
-
* worker id, reason, state, liveness, elapsed time
|
|
231
|
-
*
|
|
219
|
+
* worker id, reason, state, liveness, and elapsed time. Never includes raw
|
|
220
|
+
* payloads or full JSON dumps.
|
|
232
221
|
*/
|
|
233
|
-
export function formatEscalationForTerminal(runId, candidate
|
|
222
|
+
export function formatEscalationForTerminal(runId, candidate) {
|
|
234
223
|
const worker = candidate.worker_id ? ` worker=${candidate.worker_id}` : "";
|
|
235
224
|
const stateBit = candidate.state ? ` state=${candidate.state}` : "";
|
|
236
225
|
const liveBit = candidate.liveness ? ` liveness=${candidate.liveness}` : "";
|
|
237
226
|
const elapsed = ` elapsed=${formatElapsed(candidate.elapsed_ms)}`;
|
|
238
|
-
|
|
239
|
-
if (draftText && draftText.trim().length > 0) {
|
|
240
|
-
// Single-line the drafted text so the terminal stays scannable.
|
|
241
|
-
line += ` :: ${draftText.replace(/\s+/g, " ").trim()}`;
|
|
242
|
-
}
|
|
243
|
-
return line;
|
|
227
|
+
return `[supervisor] run=${runId}${worker} reason=${candidate.reason}${stateBit}${liveBit}${elapsed}`;
|
|
244
228
|
}
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* concurrent retries, each event carries a deterministic id derived from a
|
|
7
7
|
* STABLE idempotency key. The key is built only from normalized dimensions
|
|
8
8
|
* (run id, optional worker id, reason, kind, cooldown window) — it deliberately
|
|
9
|
-
* EXCLUDES raw event payloads
|
|
10
|
-
*
|
|
11
|
-
* constraint instead of double-emitting.
|
|
9
|
+
* EXCLUDES raw event payloads, so two assessments for the same decision
|
|
10
|
+
* collide on the `events.id` UNIQUE constraint instead of double-emitting.
|
|
12
11
|
*
|
|
13
12
|
* This module performs NO privileged action: it only writes an audit event to
|
|
14
13
|
* the local ledger.
|
|
@@ -22,7 +21,7 @@ function normalizeDimension(value) {
|
|
|
22
21
|
/**
|
|
23
22
|
* Build a stable idempotency key from normalized dimensions. Whitespace and
|
|
24
23
|
* case are normalized; empty/undefined `worker_id` collapses to a fixed
|
|
25
|
-
* run-level token. NO raw payload
|
|
24
|
+
* run-level token. NO raw payload ever enters the key.
|
|
26
25
|
*/
|
|
27
26
|
export function makeSupervisorIdempotencyKey(meta) {
|
|
28
27
|
const parts = [
|
|
@@ -75,8 +74,7 @@ export async function emitSupervisorAssessmentIfNew(input, deps = {}) {
|
|
|
75
74
|
const emitEvent = deps.emitEvent ?? emitConductorEvent;
|
|
76
75
|
const idempotencyKey = makeSupervisorIdempotencyKey(input.idempotency);
|
|
77
76
|
const eventId = makeSupervisorAssessmentEventId(idempotencyKey);
|
|
78
|
-
// Build secret-free assessment details
|
|
79
|
-
// classification/decision and optional short drafted text — never raw payload.
|
|
77
|
+
// Build secret-free deterministic assessment details — never raw payload.
|
|
80
78
|
const details = {
|
|
81
79
|
...(input.details ?? {}),
|
|
82
80
|
idempotency_key: idempotencyKey,
|
|
@@ -85,12 +83,7 @@ export async function emitSupervisorAssessmentIfNew(input, deps = {}) {
|
|
|
85
83
|
cooldown_window: input.idempotency.cooldown_window,
|
|
86
84
|
classification: input.assessment.classification,
|
|
87
85
|
confidence: input.assessment.confidence,
|
|
88
|
-
should_escalate: input.assessment.should_escalate,
|
|
89
|
-
assessment_source: input.assessment.source,
|
|
90
86
|
};
|
|
91
|
-
if (input.assessment.draft_escalation_text) {
|
|
92
|
-
details.draft_escalation_text = input.assessment.draft_escalation_text;
|
|
93
|
-
}
|
|
94
87
|
const event = {
|
|
95
88
|
id: eventId,
|
|
96
89
|
source: "conductor-supervisor",
|
|
@@ -101,7 +94,7 @@ export async function emitSupervisorAssessmentIfNew(input, deps = {}) {
|
|
|
101
94
|
observed_via: "supervisor",
|
|
102
95
|
data: {
|
|
103
96
|
summary: `supervisor assessment: ${input.idempotency.reason}`,
|
|
104
|
-
status:
|
|
97
|
+
status: "escalated",
|
|
105
98
|
reason: input.idempotency.reason,
|
|
106
99
|
details,
|
|
107
100
|
},
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Supervisor merge pipeline wrapper (Conductor C6, BAPI-398).
|
|
3
3
|
*
|
|
4
|
-
* Processes an eligible
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Processes an eligible PR-bound `gate.met` event by calling the API-owned merge
|
|
5
|
+
* decision/execution endpoint and recording the returned `merge.*` ledger events
|
|
6
|
+
* into the LOCAL conductor ledger. The merge decision depends ONLY on the PR/gate
|
|
7
|
+
* identity `(repo, pr_number, head_sha, gate)` — `worker_id` is attribution
|
|
8
|
+
* metadata that is stamped onto the emitted `merge.*` events for audit but never
|
|
9
|
+
* gates the merge (BAPI-525 Change A). When the source `gate.met` lacks a
|
|
10
|
+
* `worker_id` (e.g. the autonomous git-hook chain), attribution is resolved from
|
|
11
|
+
* durable dispatch state via an optional `resolveWorkerIdForGateEvent` seam. This
|
|
12
|
+
* module:
|
|
7
13
|
*
|
|
8
14
|
* - never performs a provider merge locally and never handles VCS write
|
|
9
15
|
* credentials (no `gh`, no GITHUB_TOKEN, no installation token, no shell-out),
|
|
@@ -52,6 +58,24 @@ function mapApiErrorReason(error) {
|
|
|
52
58
|
}
|
|
53
59
|
return "api_network";
|
|
54
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the worker attribution stamped onto emitted `merge.*` ledger events for
|
|
63
|
+
* a `gate.met`. Precedence: (1) a non-empty `event.worker_id`, (2) the durable
|
|
64
|
+
* attribution seam, (3) `null`. Attribution is audit-only and is never required
|
|
65
|
+
* for the merge decision (BAPI-525 Change A).
|
|
66
|
+
*/
|
|
67
|
+
function resolveAttributionWorkerId(event, identity, resolve) {
|
|
68
|
+
if (typeof event.worker_id === "string" && event.worker_id.trim().length > 0) {
|
|
69
|
+
return event.worker_id.trim();
|
|
70
|
+
}
|
|
71
|
+
if (resolve) {
|
|
72
|
+
const resolved = resolve(event, identity);
|
|
73
|
+
if (typeof resolved === "string" && resolved.trim().length > 0) {
|
|
74
|
+
return resolved.trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
55
79
|
/**
|
|
56
80
|
* Process an eligible `gate.met` event end-to-end: terminal-success short-circuit,
|
|
57
81
|
* API merge call, ordered ledger recording, and sanitized error handling. Never
|
|
@@ -68,6 +92,9 @@ export async function processGateMetMerge(access, event, deps = {}) {
|
|
|
68
92
|
return { processed: false, reason: "ineligible" };
|
|
69
93
|
}
|
|
70
94
|
const actionKey = identity.action_key;
|
|
95
|
+
// Attribution only — resolved from the event or durable dispatch state, never a
|
|
96
|
+
// merge gate (BAPI-525 Change A). A null value is tolerated end-to-end.
|
|
97
|
+
const attributionWorkerId = resolveAttributionWorkerId(event, identity, deps.resolveWorkerIdForGateEvent);
|
|
71
98
|
// Terminal-success short-circuit BEFORE any API call (idempotent across restart).
|
|
72
99
|
if (await checkTerminal(actionKey)) {
|
|
73
100
|
return { processed: false, reason: "already_succeeded" };
|
|
@@ -92,7 +119,7 @@ export async function processGateMetMerge(access, event, deps = {}) {
|
|
|
92
119
|
reason,
|
|
93
120
|
details: baseDetails,
|
|
94
121
|
run_id: event.run_id ?? null,
|
|
95
|
-
worker_id:
|
|
122
|
+
worker_id: attributionWorkerId,
|
|
96
123
|
summary: `merge.failed ${reason}`,
|
|
97
124
|
}, { emitEvent: deps.emitEvent });
|
|
98
125
|
return { processed: true, outcome: "api_error", reason };
|
|
@@ -119,7 +146,7 @@ export async function processGateMetMerge(access, event, deps = {}) {
|
|
|
119
146
|
reason: ledgerEvent.reason ?? null,
|
|
120
147
|
details: ledgerEvent.details ?? baseDetails,
|
|
121
148
|
run_id: event.run_id ?? null,
|
|
122
|
-
worker_id:
|
|
149
|
+
worker_id: attributionWorkerId,
|
|
123
150
|
}, { emitEvent: deps.emitEvent });
|
|
124
151
|
emitted.push({ type: ledgerEvent.type, emitted: result.emitted });
|
|
125
152
|
}
|
|
@@ -28,11 +28,8 @@ export function buildSupervisorEscalationWorkerMessage(candidate, assessment, st
|
|
|
28
28
|
state: candidate.state,
|
|
29
29
|
liveness: candidate.liveness,
|
|
30
30
|
elapsed_ms: candidate.elapsed_ms,
|
|
31
|
-
assessment_source:
|
|
31
|
+
assessment_source: "deterministic",
|
|
32
32
|
};
|
|
33
|
-
if (assessment.draft_escalation_text) {
|
|
34
|
-
details.draft_escalation_text = assessment.draft_escalation_text;
|
|
35
|
-
}
|
|
36
33
|
return {
|
|
37
34
|
run_id: state.run_id,
|
|
38
35
|
worker_id: candidate.worker_id,
|
|
@@ -40,7 +37,7 @@ export function buildSupervisorEscalationWorkerMessage(candidate, assessment, st
|
|
|
40
37
|
cause_seq: state.last_seq,
|
|
41
38
|
payload: {
|
|
42
39
|
summary: `supervisor escalation: ${candidate.reason}`,
|
|
43
|
-
status:
|
|
40
|
+
status: "escalated",
|
|
44
41
|
details,
|
|
45
42
|
},
|
|
46
43
|
source: "conductor-supervisor",
|
|
@@ -23,7 +23,7 @@ export async function dispatchSupervisorNotification(epicRunId, candidate, asses
|
|
|
23
23
|
worker_id: candidate.worker_id ?? null,
|
|
24
24
|
elapsed_ms: candidate.elapsed_ms,
|
|
25
25
|
ticket_key: (candidate.context?.ticket_key ?? null),
|
|
26
|
-
|
|
26
|
+
classification: assessment.classification,
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
29
|
const headers = {
|
|
@@ -12,13 +12,10 @@
|
|
|
12
12
|
* 2. supervisor_projection — the resumable operational mirror (this loop owns
|
|
13
13
|
* writes; crash/restart re-hydrates from it).
|
|
14
14
|
* 3. supervisor.assessment events — AUDIT ONLY; never override raw truth.
|
|
15
|
-
* 4. LLM judgment context — DISPOSABLE; advisory text only, never persisted as
|
|
16
|
-
* truth and never able to execute an action.
|
|
17
15
|
*
|
|
18
|
-
* The deterministic loop owns correctness
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* fails.
|
|
16
|
+
* The deterministic loop owns correctness and always escalates a detected
|
|
17
|
+
* candidate (BAPI-496): there is no LLM judgment boundary and no suppression
|
|
18
|
+
* path.
|
|
22
19
|
*/
|
|
23
20
|
import { waitForConductorEvent, getSupervisorSnapshot, upsertSupervisorProjection, sendWorkerMessage, } from "./store.js";
|
|
24
21
|
import { emitSupervisorAssessmentIfNew } from "./supervisor-ledger.js";
|
|
@@ -26,36 +23,18 @@ import { sendSupervisorEscalationWorkerMessageIfNew } from "./supervisor-message
|
|
|
26
23
|
import { resolveSupervisorConfig } from "./supervisor-config.js";
|
|
27
24
|
import { hydrateSupervisorRunStateFromSnapshot, applyConductorEventToSupervisorState, applySupervisorHousekeeping, isSupervisorRunTerminal, hasSupervisorGlobalTimeoutElapsed, toSupervisorProjectionInput, } from "./supervisor-state.js";
|
|
28
25
|
import { findSupervisorEscalationCandidates, shouldEmitEscalation, recordEscalationResult, formatEscalationForTerminal, } from "./supervisor-escalation.js";
|
|
29
|
-
import { assessSupervisorCandidate } from "./supervisor-judgment.js";
|
|
30
26
|
import { makeSupervisorIdempotencyKey } from "./supervisor-ledger.js";
|
|
31
|
-
import { createDefaultSupervisorJudgmentClient } from "./supervisor-judgment-python.js";
|
|
32
27
|
import { resolveConductorBridgeApiAccess, } from "./bridge-api-client.js";
|
|
33
28
|
import { extractMergeActionIdentityFromGateEvent } from "./merge-ledger.js";
|
|
34
29
|
import { processGateMetMerge } from "./supervisor-merge.js";
|
|
35
30
|
import { dispatchSupervisorNotification } from "./supervisor-notification.js";
|
|
36
31
|
import { ConductorValidationError } from "./errors.js";
|
|
37
|
-
/**
|
|
38
|
-
function compactWorkerForJudgment(worker) {
|
|
39
|
-
if (!worker)
|
|
40
|
-
return null;
|
|
41
|
-
return {
|
|
42
|
-
worker_id: worker.worker_id,
|
|
43
|
-
ticket_key: worker.ticket_key,
|
|
44
|
-
state: worker.state,
|
|
45
|
-
liveness: worker.liveness,
|
|
46
|
-
last_event_time: worker.last_event_time,
|
|
47
|
-
last_progress_time: worker.last_progress_time,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/** Deterministic (degraded) assessment for an unambiguous candidate. */
|
|
32
|
+
/** Deterministic assessment for an escalation candidate. */
|
|
51
33
|
function deterministicAssessment(candidate) {
|
|
52
34
|
return {
|
|
53
35
|
classification: "stuck",
|
|
54
36
|
confidence: 1,
|
|
55
|
-
should_escalate: true,
|
|
56
37
|
reason: candidate.reason,
|
|
57
|
-
draft_escalation_text: null,
|
|
58
|
-
source: "degraded",
|
|
59
38
|
};
|
|
60
39
|
}
|
|
61
40
|
/** Resolve the terminal run status from worker outcomes. */
|
|
@@ -64,20 +43,20 @@ function terminalStatus(state) {
|
|
|
64
43
|
return anyFailed ? "failed" : "complete";
|
|
65
44
|
}
|
|
66
45
|
/**
|
|
67
|
-
* Process the escalation pipeline for the current state: find candidates,
|
|
68
|
-
*
|
|
46
|
+
* Process the escalation pipeline for the current state: find candidates,
|
|
47
|
+
* deterministically assess them, emit idempotent assessment events, print
|
|
69
48
|
* terminal lines for newly-emitted escalations, and record outcomes. Returns
|
|
70
49
|
* nothing; it mutates `state`.
|
|
71
50
|
*/
|
|
72
|
-
async function processEscalations(state, config,
|
|
51
|
+
async function processEscalations(state, config, deps) {
|
|
73
52
|
const now = deps.now();
|
|
74
53
|
const candidates = findSupervisorEscalationCandidates(state, config, now);
|
|
75
54
|
for (const candidate of candidates) {
|
|
76
55
|
const decision = shouldEmitEscalation(state, candidate, config, now);
|
|
77
56
|
if (!decision.emit)
|
|
78
57
|
continue;
|
|
79
|
-
// Compute the idempotency key up front so
|
|
80
|
-
//
|
|
58
|
+
// Compute the idempotency key up front so the decision in this window can
|
|
59
|
+
// be recorded under it.
|
|
81
60
|
const idempotency = {
|
|
82
61
|
run_id: state.run_id,
|
|
83
62
|
worker_id: candidate.worker_id,
|
|
@@ -86,28 +65,8 @@ async function processEscalations(state, config, client, deps) {
|
|
|
86
65
|
cooldown_window: decision.cooldown_window,
|
|
87
66
|
};
|
|
88
67
|
const idempotencyKey = makeSupervisorIdempotencyKey(idempotency);
|
|
89
|
-
|
|
90
|
-
if (candidate.ambiguous) {
|
|
91
|
-
assessment = await assessSupervisorCandidate({
|
|
92
|
-
run_id: state.run_id,
|
|
93
|
-
candidate,
|
|
94
|
-
worker: candidate.worker_id ? compactWorkerForJudgment(state.workers[candidate.worker_id]) : null,
|
|
95
|
-
}, config, state.llm_budget, client);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
assessment = deterministicAssessment(candidate);
|
|
99
|
-
}
|
|
68
|
+
const assessment = deterministicAssessment(candidate);
|
|
100
69
|
state.latest_assessment = assessment;
|
|
101
|
-
if (!assessment.should_escalate) {
|
|
102
|
-
// The judgment declined to escalate. Record a `suppressed` decision for
|
|
103
|
-
// this cooldown window so the candidate is NOT re-judged every wake
|
|
104
|
-
// interval — without this, a persistently-ambiguous worker would re-query
|
|
105
|
-
// the LLM each tick, exhaust the per-run budget, and then escalate anyway
|
|
106
|
-
// once the degraded fallback kicks in. The decision stands until the
|
|
107
|
-
// window rolls over.
|
|
108
|
-
recordEscalationResult(state, candidate, decision.cooldown_window, idempotencyKey, "suppressed", now);
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
70
|
let outcome = "skipped";
|
|
112
71
|
try {
|
|
113
72
|
const result = await deps.emitAssessment({
|
|
@@ -145,7 +104,7 @@ async function processEscalations(state, config, client, deps) {
|
|
|
145
104
|
// Only print on a genuinely NEW escalation — never re-spam a duplicate after
|
|
146
105
|
// a crash/restart within the same cooldown window.
|
|
147
106
|
if (outcome === "emitted") {
|
|
148
|
-
deps.log(formatEscalationForTerminal(state.run_id, candidate
|
|
107
|
+
deps.log(formatEscalationForTerminal(state.run_id, candidate));
|
|
149
108
|
// S7: dispatch an out-of-band human notification for newly emitted escalations.
|
|
150
109
|
// Best-effort: a failure must never stop supervising.
|
|
151
110
|
if (deps.dispatchNotification) {
|
|
@@ -179,7 +138,6 @@ export async function runSupervisor(options, deps = {}) {
|
|
|
179
138
|
const upsertProjection = deps.upsertProjection ?? upsertSupervisorProjection;
|
|
180
139
|
const emitAssessment = deps.emitAssessment ?? emitSupervisorAssessmentIfNew;
|
|
181
140
|
const sendMessage = deps.sendWorkerMessage ?? sendWorkerMessage;
|
|
182
|
-
const judgmentClient = deps.judgmentClient ?? createDefaultSupervisorJudgmentClient(config);
|
|
183
141
|
const resolveBridgeAccess = deps.resolveBridgeAccess ?? (() => resolveConductorBridgeApiAccess());
|
|
184
142
|
const processMerge = deps.processMerge ?? processGateMetMerge;
|
|
185
143
|
const dispatchNotification = deps.dispatchNotification ?? dispatchSupervisorNotification;
|
|
@@ -304,7 +262,7 @@ export async function runSupervisor(options, deps = {}) {
|
|
|
304
262
|
// Housekeeping (liveness + state-specific stalls) runs every iteration so a
|
|
305
263
|
// timeout with no events still advances liveness/stall state.
|
|
306
264
|
applySupervisorHousekeeping(state, config, now());
|
|
307
|
-
await processEscalations(state, config,
|
|
265
|
+
await processEscalations(state, config, {
|
|
308
266
|
emitAssessment,
|
|
309
267
|
sendWorkerMessage: sendMessage,
|
|
310
268
|
log,
|
|
@@ -48,11 +48,6 @@ export function createEmptySupervisorRunState(runId, config, now) {
|
|
|
48
48
|
gates: {},
|
|
49
49
|
latest_assessment: null,
|
|
50
50
|
escalations: [],
|
|
51
|
-
llm_budget: {
|
|
52
|
-
enabled: config.llm_enabled,
|
|
53
|
-
max_calls: config.llm_max_calls,
|
|
54
|
-
used_calls: 0,
|
|
55
|
-
},
|
|
56
51
|
started_at: startedIso,
|
|
57
52
|
updated_at: startedIso,
|
|
58
53
|
global_deadline_at: msToIso(now + config.global_timeout_ms),
|
|
@@ -93,15 +88,8 @@ export function hydrateSupervisorRunStateFromSnapshot(snapshot, runId, config, n
|
|
|
93
88
|
escalations: Array.isArray(summary.escalations)
|
|
94
89
|
? summary.escalations
|
|
95
90
|
: [],
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
enabled: config.llm_enabled,
|
|
99
|
-
max_calls: config.llm_max_calls,
|
|
100
|
-
used_calls: typeof summary.llm_budget.used_calls === "number"
|
|
101
|
-
? summary.llm_budget.used_calls
|
|
102
|
-
: 0,
|
|
103
|
-
}
|
|
104
|
-
: empty.llm_budget,
|
|
91
|
+
// A legacy pre-BAPI-496 LLM-budget summary key is intentionally ignored
|
|
92
|
+
// rather than treated as an error — old projections still carry it.
|
|
105
93
|
started_at: typeof summary.started_at === "string" ? summary.started_at : empty.started_at,
|
|
106
94
|
global_deadline_at: typeof summary.global_deadline_at === "string" ? summary.global_deadline_at : empty.global_deadline_at,
|
|
107
95
|
roster_discovered: summary.roster_discovered === true,
|
|
@@ -536,11 +524,10 @@ function compactWorker(worker) {
|
|
|
536
524
|
* holds compact worker summaries (never raw event data); `gates` holds gate
|
|
537
525
|
* metadata; `assessment` holds the latest assessment or null; and `summary`
|
|
538
526
|
* holds the FULL resumable supervisor state (worker watchdog states, liveness,
|
|
539
|
-
* last seq, escalation history,
|
|
527
|
+
* last seq, escalation history, run start/deadline metadata).
|
|
540
528
|
*
|
|
541
529
|
* The serialized summary is secret-free by construction: it is built only from
|
|
542
|
-
* the compact state fields above — never from raw payloads
|
|
543
|
-
* prompts.
|
|
530
|
+
* the compact state fields above — never from raw payloads or secrets.
|
|
544
531
|
*/
|
|
545
532
|
export function toSupervisorProjectionInput(state) {
|
|
546
533
|
const summary = {
|
|
@@ -553,7 +540,6 @@ export function toSupervisorProjectionInput(state) {
|
|
|
553
540
|
gates: state.gates,
|
|
554
541
|
latest_assessment: state.latest_assessment,
|
|
555
542
|
escalations: state.escalations,
|
|
556
|
-
llm_budget: state.llm_budget,
|
|
557
543
|
started_at: state.started_at,
|
|
558
544
|
updated_at: state.updated_at,
|
|
559
545
|
global_deadline_at: state.global_deadline_at,
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* (BAPI-396, conductor C4).
|
|
4
4
|
*
|
|
5
5
|
* These are pure TypeScript type declarations with NO runtime behavior so every
|
|
6
|
-
* supervisor module (config, reducer, escalation,
|
|
7
|
-
*
|
|
6
|
+
* supervisor module (config, reducer, escalation, runtime) can depend on them
|
|
7
|
+
* freely without import cycles or side effects.
|
|
8
8
|
*
|
|
9
9
|
* Vocabulary split — the supervisor tracks two ORTHOGONAL axes per worker:
|
|
10
10
|
* - {@link WatchdogState}: the worker's progress through the run lifecycle
|
|
@@ -34,6 +34,10 @@ export const SEMANTIC_EVENT_TYPES = [
|
|
|
34
34
|
"merge.attempted",
|
|
35
35
|
"merge.succeeded",
|
|
36
36
|
"merge.failed",
|
|
37
|
+
// BAPI-494: a PR that cannot be merged (CONFLICTING/DIRTY). A terminal,
|
|
38
|
+
// head-scoped blocking signal that folds to `blocked` and routes through the
|
|
39
|
+
// BAPI-441 remediation pass as a resume-mode redispatch.
|
|
40
|
+
"merge.conflict",
|
|
37
41
|
"merge.pending_approval",
|
|
38
42
|
// BAPI-440 PR review-state telemetry event types.
|
|
39
43
|
"review.passed",
|
|
@@ -47,6 +51,14 @@ export const SEMANTIC_EVENT_TYPES = [
|
|
|
47
51
|
// post-merge repository re-index, so the (stateless) reconcile loop can fold a
|
|
48
52
|
// merged ticket to `done` from the ledger instead of an in-memory wait map.
|
|
49
53
|
"parse.triggered",
|
|
54
|
+
// BAPI-527: durable parse-after-merge TERMINAL markers. Emitted by epic-tick
|
|
55
|
+
// once the durable `/jira/parse-status` surface reports a terminal outcome for a
|
|
56
|
+
// triggered post-merge parse: `parse.succeeded` lets the merge.succeeded signal
|
|
57
|
+
// proceed to `done`; `parse.failed` folds the ticket to `blocked` (and escalates).
|
|
58
|
+
// Payloads are compact (summary/status/reason/details with epic_key + ticket_key
|
|
59
|
+
// + bounded parse error metadata) — never subprocess stdout/stderr/stacks/commands.
|
|
60
|
+
"parse.succeeded",
|
|
61
|
+
"parse.failed",
|
|
50
62
|
];
|
|
51
63
|
/**
|
|
52
64
|
* Type guard: returns `true` only when `value` is one of the exact taxonomy
|
package/build/conductor/tools.js
CHANGED
|
@@ -11,9 +11,14 @@
|
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { SEMANTIC_EVENT_TYPES } from "./taxonomy.js";
|
|
13
13
|
import { ConductorValidationError, toConductorErrorEnvelope } from "./errors.js";
|
|
14
|
-
import { emitConductorEvent, pollConductorEvents, waitForConductorEvent, getSupervisorSnapshot, sendWorkerMessage,
|
|
14
|
+
import { emitConductorEvent, pollConductorEvents, waitForConductorEvent, getSupervisorSnapshot, sendWorkerMessage, } from "./store.js";
|
|
15
15
|
import { normalizePrNumber, normalizeSha } from "./git-ci-types.js";
|
|
16
16
|
import { waitForDoneGate, resolveDispatchRunIdForBinding } from "./pr-ci-producer.js";
|
|
17
|
+
// BAPI-527: worker-facing ledger operations run through the conductor CLI
|
|
18
|
+
// subprocess (under the captured conductor Node), NOT the in-process SQLite store,
|
|
19
|
+
// so the worker Node never loads the `better-sqlite3` native binary. `store.ts`'s
|
|
20
|
+
// `checkWorkerMessages` is intentionally NOT imported here anymore.
|
|
21
|
+
import { checkWorkerMessagesViaCli, emitConductorEventIfNewViaCli } from "./worker-ledger-cli.js";
|
|
17
22
|
import { resolveConductorBridgeApiAccess, fetchEpicRunState, ConductorBridgeApiError } from "./bridge-api-client.js";
|
|
18
23
|
/** Build a Zod enum from the semantic taxonomy so arbitrary types are rejected up front. */
|
|
19
24
|
export function buildEventTypeZodEnum() {
|
|
@@ -37,7 +42,10 @@ function jsonResult(value) {
|
|
|
37
42
|
/**
|
|
38
43
|
* Wrap a tool handler so any thrown value becomes a sanitized structured JSON
|
|
39
44
|
* error envelope (validation -> 400, store-busy -> 503, otherwise 500). Raw
|
|
40
|
-
* input data, stack traces, and secret material are never echoed back.
|
|
45
|
+
* input data, stack traces, and secret material are never echoed back. Any
|
|
46
|
+
* allowlisted structured `details` on the envelope (e.g. the BAPI-526
|
|
47
|
+
* LEDGER_NATIVE_MODULE_LOAD_FAILED node_version/ABI/module fields) is preserved
|
|
48
|
+
* through {@link jsonResult}'s pretty serialization unchanged.
|
|
41
49
|
*/
|
|
42
50
|
export function withConductorToolErrorHandling(handler) {
|
|
43
51
|
return async (args) => {
|
|
@@ -249,7 +257,17 @@ function registerWaitForDoneGateTool(registerTool) {
|
|
|
249
257
|
timeoutMs: args.timeout_ms,
|
|
250
258
|
pollIntervalMs: args.poll_interval_ms,
|
|
251
259
|
worktreePath: args.worktree_path,
|
|
252
|
-
}, {
|
|
260
|
+
}, {
|
|
261
|
+
resolveRunId: resolveDispatchRunIdForBinding,
|
|
262
|
+
// BAPI-527: route the gate's ENTIRE dedup+emit through the conductor CLI
|
|
263
|
+
// subprocess. Injecting `emitIfNew` (not just the write sink) replaces the
|
|
264
|
+
// in-process `emitConductorEventIfNew`, whose `eventAlreadyExists` poll
|
|
265
|
+
// pre-check would load better-sqlite3 in the worker Node. The dedupe key +
|
|
266
|
+
// deterministic id are derived purely and the id is forwarded to the CLI,
|
|
267
|
+
// so dedup happens server-side on the events.id UNIQUE constraint — the
|
|
268
|
+
// worker path performs NO in-process ledger read or write.
|
|
269
|
+
emitIfNew: (input, dimensions) => emitConductorEventIfNewViaCli(input, dimensions),
|
|
270
|
+
});
|
|
253
271
|
return jsonResult({
|
|
254
272
|
gate_met: result.gate_met,
|
|
255
273
|
timed_out: result.timed_out,
|
|
@@ -338,9 +356,13 @@ function registerCheckMessagesTool(registerTool) {
|
|
|
338
356
|
if (runId.trim().length === 0 || workerId.trim().length === 0) {
|
|
339
357
|
throw new ConductorValidationError("Conductor worker identity is unavailable: provide run_id + worker_id, or set BAPI_CONDUCTOR_RUN_ID and BAPI_CONDUCTOR_WORKER_ID.");
|
|
340
358
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
359
|
+
// BAPI-527: read+ack through the conductor CLI subprocess (captured
|
|
360
|
+
// conductor Node), NOT the in-process store, so the worker Node never loads
|
|
361
|
+
// better-sqlite3. A missing/invalid CONDUCTOR_NODE_PATH surfaces as the
|
|
362
|
+
// typed LEDGER_SUBPROCESS_RUNTIME_UNAVAILABLE envelope via the wrapper below.
|
|
363
|
+
const result = await checkWorkerMessagesViaCli({
|
|
364
|
+
runId,
|
|
365
|
+
workerId,
|
|
344
366
|
limit: args.limit,
|
|
345
367
|
});
|
|
346
368
|
return jsonResult(result);
|