@plexor-dev/claude-code-plugin-staging 0.1.0-beta.26 → 0.1.0-beta.27
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/lib/supervisor.js +282 -0
- package/package.json +1 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plexor Supervisor Emitter — Phases 1-4
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: Basic routing summary
|
|
5
|
+
* [PLEXOR: Routed to {provider}/{model}, {latency}ms, {routing_source}]
|
|
6
|
+
*
|
|
7
|
+
* Phase 2: Enhanced routing with cohort from response body fields
|
|
8
|
+
* [PLEXOR: {provider}/{model}, {latency}ms, {source} | {cohort}]
|
|
9
|
+
*
|
|
10
|
+
* Phase 3: Zero-tool escalation detection (agent_halt / escalation signals)
|
|
11
|
+
* [PLEXOR: Zero-tool escalation: {provider1} → {provider2}]
|
|
12
|
+
*
|
|
13
|
+
* Phase 4: Scaffolding gate blocked detection
|
|
14
|
+
* [PLEXOR: Scaffolding gate: {model} blocked, using {alternative}]
|
|
15
|
+
*
|
|
16
|
+
* This module is consumed by track-response.js to surface routing
|
|
17
|
+
* decisions to the developer without requiring them to parse verbose logs.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const CYAN = '\x1b[36m';
|
|
21
|
+
const YELLOW = '\x1b[33m';
|
|
22
|
+
const RED = '\x1b[31m';
|
|
23
|
+
const RESET = '\x1b[0m';
|
|
24
|
+
|
|
25
|
+
class SupervisorEmitter {
|
|
26
|
+
/**
|
|
27
|
+
* @param {object} [opts]
|
|
28
|
+
* @param {boolean} [opts.enabled] — honour PLEXOR_SUPERVISOR env var (default true)
|
|
29
|
+
*/
|
|
30
|
+
constructor(opts = {}) {
|
|
31
|
+
const envFlag = process.env.PLEXOR_SUPERVISOR;
|
|
32
|
+
if (envFlag !== undefined) {
|
|
33
|
+
this.enabled = !/^(0|false|no|off)$/i.test(String(envFlag));
|
|
34
|
+
} else {
|
|
35
|
+
this.enabled = opts.enabled !== false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build the Phase 2 enhanced supervisor summary from a gateway response.
|
|
41
|
+
* Reads plexor_provider_used, plexor_selected_model, plexor_latency_ms,
|
|
42
|
+
* plexor_routing_source from the response body (not just headers).
|
|
43
|
+
*
|
|
44
|
+
* Format: [PLEXOR: provider/model, latencyms, source | cohort]
|
|
45
|
+
*
|
|
46
|
+
* @param {object} response — the full LLM response object
|
|
47
|
+
* @param {object} [plexorMeta] — the _plexor metadata block (may be absent)
|
|
48
|
+
* @returns {string|null}
|
|
49
|
+
*/
|
|
50
|
+
buildSummary(response, plexorMeta) {
|
|
51
|
+
if (!response || typeof response !== 'object') {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const provider = this._resolveProvider(response, plexorMeta);
|
|
56
|
+
const model = this._resolveModel(response, plexorMeta);
|
|
57
|
+
const latencyMs = this._resolveLatency(response, plexorMeta);
|
|
58
|
+
const routingSource = this._resolveRoutingSource(response, plexorMeta);
|
|
59
|
+
const cohort = this._resolveCohort(response, plexorMeta);
|
|
60
|
+
|
|
61
|
+
// Need at least provider or model to emit anything useful
|
|
62
|
+
if (!provider && !model) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const target = [provider, model].filter(Boolean).join('/');
|
|
67
|
+
const parts = [target];
|
|
68
|
+
|
|
69
|
+
if (latencyMs !== null) {
|
|
70
|
+
parts.push(`${latencyMs}ms`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (routingSource) {
|
|
74
|
+
parts.push(routingSource);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let line = parts.join(', ');
|
|
78
|
+
|
|
79
|
+
if (cohort) {
|
|
80
|
+
line += ` | ${cohort}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return `[PLEXOR: ${line}]`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Phase 3: Detect zero-tool escalation signals in the response.
|
|
88
|
+
* Fires when agent_halt is set or escalation_chain / fallback provider data
|
|
89
|
+
* indicates a provider switch due to tool incapability.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} response
|
|
92
|
+
* @param {object} [plexorMeta]
|
|
93
|
+
* @returns {string|null} — escalation message or null
|
|
94
|
+
*/
|
|
95
|
+
buildEscalationNotice(response, plexorMeta) {
|
|
96
|
+
if (!response || typeof response !== 'object') {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const agentHalt = this._toBool(
|
|
101
|
+
response?.plexor_agent_halt ??
|
|
102
|
+
response?.plexor?.agent_halt ??
|
|
103
|
+
plexorMeta?.agent_halt
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const escalationChain =
|
|
107
|
+
response?.plexor_escalation_chain ||
|
|
108
|
+
response?.plexor?.escalation_chain ||
|
|
109
|
+
plexorMeta?.escalation_chain ||
|
|
110
|
+
null;
|
|
111
|
+
|
|
112
|
+
const fallbackUsed = this._toBool(
|
|
113
|
+
response?.plexor_fallback_used ??
|
|
114
|
+
response?.fallback_used ??
|
|
115
|
+
response?.plexor?.fallback_used
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const originalProvider =
|
|
119
|
+
response?.plexor_original_provider ||
|
|
120
|
+
response?.plexor?.original_provider ||
|
|
121
|
+
plexorMeta?.original_provider ||
|
|
122
|
+
null;
|
|
123
|
+
|
|
124
|
+
const currentProvider = this._resolveProvider(response, plexorMeta);
|
|
125
|
+
|
|
126
|
+
// Case 1: Explicit escalation chain present (e.g., ["openai-mini", "openai"])
|
|
127
|
+
if (Array.isArray(escalationChain) && escalationChain.length >= 2) {
|
|
128
|
+
const from = escalationChain[0];
|
|
129
|
+
const to = escalationChain[escalationChain.length - 1];
|
|
130
|
+
return `[PLEXOR: Zero-tool escalation: ${from} \u2192 ${to}]`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Case 2: agent_halt with fallback — provider switched
|
|
134
|
+
if (agentHalt && fallbackUsed && originalProvider && currentProvider && originalProvider !== currentProvider) {
|
|
135
|
+
return `[PLEXOR: Zero-tool escalation: ${originalProvider} \u2192 ${currentProvider}]`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Case 3: agent_halt alone (escalation happened but we may not know the full chain)
|
|
139
|
+
if (agentHalt && fallbackUsed) {
|
|
140
|
+
const from = originalProvider || 'original';
|
|
141
|
+
const to = currentProvider || 'fallback';
|
|
142
|
+
return `[PLEXOR: Zero-tool escalation: ${from} \u2192 ${to}]`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Phase 4: Detect scaffolding gate blocks.
|
|
150
|
+
* Fires when scaffolding_gate_blocked is present in the response,
|
|
151
|
+
* indicating a model was blocked by the scaffolding gate and an
|
|
152
|
+
* alternative was used.
|
|
153
|
+
*
|
|
154
|
+
* @param {object} response
|
|
155
|
+
* @param {object} [plexorMeta]
|
|
156
|
+
* @returns {string|null}
|
|
157
|
+
*/
|
|
158
|
+
buildScaffoldingGateNotice(response, plexorMeta) {
|
|
159
|
+
if (!response || typeof response !== 'object') {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const gateBlocked = this._toBool(
|
|
164
|
+
response?.plexor_scaffolding_gate_blocked ??
|
|
165
|
+
response?.scaffolding_gate_blocked ??
|
|
166
|
+
response?.plexor?.scaffolding_gate_blocked ??
|
|
167
|
+
plexorMeta?.scaffolding_gate_blocked
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (!gateBlocked) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const blockedModel =
|
|
175
|
+
response?.plexor_scaffolding_blocked_model ||
|
|
176
|
+
response?.plexor?.scaffolding_blocked_model ||
|
|
177
|
+
plexorMeta?.scaffolding_blocked_model ||
|
|
178
|
+
response?.plexor_original_model ||
|
|
179
|
+
response?.plexor?.original_model ||
|
|
180
|
+
plexorMeta?.original_model ||
|
|
181
|
+
'model';
|
|
182
|
+
|
|
183
|
+
const alternative =
|
|
184
|
+
response?.plexor_selected_model ||
|
|
185
|
+
response?.plexor?.selected_model ||
|
|
186
|
+
plexorMeta?.recommended_model ||
|
|
187
|
+
response?.model ||
|
|
188
|
+
'alternative';
|
|
189
|
+
|
|
190
|
+
return `[PLEXOR: Scaffolding gate: ${blockedModel} blocked, using ${alternative}]`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Emit all applicable supervisor lines to stderr if enabled.
|
|
195
|
+
*
|
|
196
|
+
* @param {object} response
|
|
197
|
+
* @param {object} [plexorMeta]
|
|
198
|
+
*/
|
|
199
|
+
emit(response, plexorMeta) {
|
|
200
|
+
if (!this.enabled) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Phase 4: Scaffolding gate (highest priority — emit first if present)
|
|
205
|
+
const scaffoldingNotice = this.buildScaffoldingGateNotice(response, plexorMeta);
|
|
206
|
+
if (scaffoldingNotice) {
|
|
207
|
+
process.stderr.write(`${RED}${scaffoldingNotice}${RESET}\n`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Phase 3: Escalation notice
|
|
211
|
+
const escalationNotice = this.buildEscalationNotice(response, plexorMeta);
|
|
212
|
+
if (escalationNotice) {
|
|
213
|
+
process.stderr.write(`${YELLOW}${escalationNotice}${RESET}\n`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Phase 2: Enhanced routing summary (always emitted when data available)
|
|
217
|
+
const summary = this.buildSummary(response, plexorMeta);
|
|
218
|
+
if (summary) {
|
|
219
|
+
process.stderr.write(`${CYAN}${summary}${RESET}\n`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ---- private helpers ----
|
|
224
|
+
|
|
225
|
+
_resolveProvider(response, meta) {
|
|
226
|
+
return (
|
|
227
|
+
response?.plexor_provider_used ||
|
|
228
|
+
response?.plexor?.provider_used ||
|
|
229
|
+
meta?.recommended_provider ||
|
|
230
|
+
null
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
_resolveModel(response, meta) {
|
|
235
|
+
return (
|
|
236
|
+
response?.plexor_selected_model ||
|
|
237
|
+
response?.plexor?.selected_model ||
|
|
238
|
+
meta?.recommended_model ||
|
|
239
|
+
response?.model ||
|
|
240
|
+
null
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
_resolveLatency(response, meta) {
|
|
245
|
+
const raw =
|
|
246
|
+
response?.plexor_latency_ms ??
|
|
247
|
+
response?.plexor?.latency_ms ??
|
|
248
|
+
meta?.latency_ms ??
|
|
249
|
+
null;
|
|
250
|
+
if (raw === null || raw === undefined) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const n = Number(raw);
|
|
254
|
+
return Number.isFinite(n) ? Math.round(n) : null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
_resolveRoutingSource(response, meta) {
|
|
258
|
+
return (
|
|
259
|
+
response?.plexor_routing_source ||
|
|
260
|
+
response?.plexor?.routing_source ||
|
|
261
|
+
meta?.source ||
|
|
262
|
+
null
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
_resolveCohort(response, meta) {
|
|
267
|
+
return (
|
|
268
|
+
response?.plexor_cohort ||
|
|
269
|
+
response?.plexor?.cohort ||
|
|
270
|
+
meta?.cohort ||
|
|
271
|
+
null
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
_toBool(value) {
|
|
276
|
+
if (value === true || value === 'true' || value === '1' || value === 1) return true;
|
|
277
|
+
if (value === false || value === 'false' || value === '0' || value === 0) return false;
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = { SupervisorEmitter };
|
package/package.json
CHANGED