@polderlabs/bizar-omp 0.6.1 → 0.6.2
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/README.md +5 -2
- package/dist/cli/agents-ink.d.ts.map +1 -1
- package/dist/cli/agents-ink.js +11 -10
- package/dist/cli/agents-ink.js.map +1 -1
- package/dist/cli/directory-selector.d.ts +36 -0
- package/dist/cli/directory-selector.d.ts.map +1 -1
- package/dist/cli/directory-selector.js +25 -0
- package/dist/cli/directory-selector.js.map +1 -1
- package/dist/cli/main.js +0 -0
- package/dist/cli/omb.js +0 -0
- package/dist/extension.d.ts +2 -1
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +160 -33
- package/dist/extension.js.map +1 -1
- package/dist/omp/daemon-service.d.ts.map +1 -1
- package/dist/omp/daemon-service.js +6 -3
- package/dist/omp/daemon-service.js.map +1 -1
- package/dist/omp/dashboard-html.d.ts.map +1 -1
- package/dist/omp/dashboard-html.js +360 -38
- package/dist/omp/dashboard-html.js.map +1 -1
- package/dist/omp/model-role-assignment.d.ts +166 -1
- package/dist/omp/model-role-assignment.d.ts.map +1 -1
- package/dist/omp/model-role-assignment.js +393 -4
- package/dist/omp/model-role-assignment.js.map +1 -1
- package/dist/omp/omb-sessions.d.ts +20 -0
- package/dist/omp/omb-sessions.d.ts.map +1 -1
- package/dist/omp/omb-sessions.js +130 -40
- package/dist/omp/omb-sessions.js.map +1 -1
- package/dist/omp/omb-settings.d.ts +16 -0
- package/dist/omp/omb-settings.d.ts.map +1 -1
- package/dist/omp/omb-settings.js +55 -3
- package/dist/omp/omb-settings.js.map +1 -1
- package/dist/omp/run-log.d.ts +26 -0
- package/dist/omp/run-log.d.ts.map +1 -0
- package/dist/omp/run-log.js +24 -0
- package/dist/omp/run-log.js.map +1 -0
- package/dist/omp/tmux.d.ts +20 -2
- package/dist/omp/tmux.d.ts.map +1 -1
- package/dist/omp/tmux.js +101 -43
- package/dist/omp/tmux.js.map +1 -1
- package/dist/omp/tray.js +1 -1
- package/dist/omp/tray.js.map +1 -1
- package/docs/compatibility/baseline.json +1 -1
- package/docs/decisions/console-handoff.md +5 -0
- package/docs/releases/0.6.2.md +32 -0
- package/package.json +1 -1
- package/skills/bizar-omp/SKILL.md +2 -1
|
@@ -126,7 +126,172 @@ export declare const BIZAR_ROLE_PROFILES: readonly RoleProfile[];
|
|
|
126
126
|
export declare const BIZAR_MODEL_ROLE_AGENT_PROMPT: string;
|
|
127
127
|
export declare function isLocalModel(model: AvailableModel): boolean;
|
|
128
128
|
export declare function planBizarModelRoles(query: ModelQuery): RoleAssignmentPlan;
|
|
129
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Pure mapping copy: applies a plan onto a settings map without mutating or
|
|
131
|
+
* validating it. Staleness is checked by the caller that owns the write, so no
|
|
132
|
+
* caller can pass a hash of the same object it is about to overwrite.
|
|
133
|
+
*/
|
|
134
|
+
export declare function applyRoleAssignmentPlan(current: Record<string, unknown>, plan: RoleAssignmentPlan): Record<string, unknown>;
|
|
130
135
|
export declare function formatRoleAssignmentPlan(plan: RoleAssignmentPlan): string;
|
|
136
|
+
/**
|
|
137
|
+
* Native tool the agent calls to persist a reviewed distribution. The command
|
|
138
|
+
* handler cannot make a model call itself, so `/bizar-models` gathers the
|
|
139
|
+
* catalog, then hands the bounded evaluation to the active agent, which must
|
|
140
|
+
* call this tool for anything to be written.
|
|
141
|
+
*/
|
|
142
|
+
export declare const BIZAR_MODEL_ROLE_TOOL = "bizar_apply_model_roles";
|
|
143
|
+
/**
|
|
144
|
+
* Longest operator notice OMP's status line can carry without clipping.
|
|
145
|
+
*
|
|
146
|
+
* `ctx.ui.notify(message, "info")` renders through the host status line, which
|
|
147
|
+
* builds exactly one text component and folds it onto two screen lines at the
|
|
148
|
+
* default 80-column terminal width — roughly 300 characters. The model-role
|
|
149
|
+
* command's own report (catalog movement, headline, and a line per role) runs
|
|
150
|
+
* far past that, so the tail silently never appears. A `warning`/`error` notice
|
|
151
|
+
* is not an alternative: those draw the full error banner.
|
|
152
|
+
*/
|
|
153
|
+
export declare const OPERATOR_NOTICE_LIMIT = 300;
|
|
154
|
+
/**
|
|
155
|
+
* Fit a report into the status line, clipping from the front. Catalog movement
|
|
156
|
+
* and the applied-map verdict sit before the per-role lines, so a truncated
|
|
157
|
+
* notice always keeps the lines that say what happened and loses the tail.
|
|
158
|
+
*/
|
|
159
|
+
export declare function clipOperatorNotice(report: string, limit?: number): string;
|
|
160
|
+
/** Settings surface role assignment needs, with both members guaranteed present. */
|
|
161
|
+
export interface RoleAssignmentSettings {
|
|
162
|
+
get(path: string): unknown;
|
|
163
|
+
set(path: string, value: unknown): void;
|
|
164
|
+
}
|
|
165
|
+
export type RoleAssignmentRoute = {
|
|
166
|
+
kind: "agent";
|
|
167
|
+
settings: RoleAssignmentSettings;
|
|
168
|
+
models: AvailableModel[];
|
|
169
|
+
send: (content: string) => void;
|
|
170
|
+
} | {
|
|
171
|
+
kind: "local-plan";
|
|
172
|
+
settings: RoleAssignmentSettings;
|
|
173
|
+
models: AvailableModel[];
|
|
174
|
+
} | {
|
|
175
|
+
kind: "agent-prompt-only";
|
|
176
|
+
send: (content: string) => void;
|
|
177
|
+
} | {
|
|
178
|
+
kind: "unsupported";
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Decide how this host can distribute roles. An agent evaluation needs both the
|
|
182
|
+
* live catalog and a way to reach the agent; a host without agent messaging
|
|
183
|
+
* falls back to deterministic scoring; a host without any of it cannot assign
|
|
184
|
+
* roles and says so instead of writing a partial mapping.
|
|
185
|
+
*/
|
|
186
|
+
export declare function roleAssignmentRoute(host: {
|
|
187
|
+
settings?: {
|
|
188
|
+
get?(path: string): unknown;
|
|
189
|
+
set?(path: string, value: unknown): void;
|
|
190
|
+
};
|
|
191
|
+
models?: readonly AvailableModel[];
|
|
192
|
+
sendUserMessage?: (content: string, options?: {
|
|
193
|
+
deliverAs?: "steer" | "followUp" | "aside";
|
|
194
|
+
}) => void;
|
|
195
|
+
}): RoleAssignmentRoute;
|
|
196
|
+
/** One model's normalized capability fields. Missing metadata stays explicitly unknown. */
|
|
197
|
+
export interface CatalogModelRecord {
|
|
198
|
+
selector: string;
|
|
199
|
+
fields: Record<string, string>;
|
|
200
|
+
}
|
|
201
|
+
/** Catalog observation persisted between `/bizar-models` invocations. */
|
|
202
|
+
export interface CatalogSnapshot {
|
|
203
|
+
digest: string;
|
|
204
|
+
capturedAt: number;
|
|
205
|
+
models: CatalogModelRecord[];
|
|
206
|
+
}
|
|
207
|
+
export interface CatalogChange {
|
|
208
|
+
kind: "added" | "removed" | "changed";
|
|
209
|
+
selector: string;
|
|
210
|
+
detail: string;
|
|
211
|
+
}
|
|
212
|
+
export interface CatalogDelta {
|
|
213
|
+
firstObservation: boolean;
|
|
214
|
+
added: CatalogChange[];
|
|
215
|
+
removed: CatalogChange[];
|
|
216
|
+
changed: CatalogChange[];
|
|
217
|
+
unchanged: number;
|
|
218
|
+
modelCount: number;
|
|
219
|
+
}
|
|
220
|
+
/** Normalize a catalog into a comparable, digestible observation. */
|
|
221
|
+
export declare function snapshotCatalog(models: readonly AvailableModel[], capturedAt?: number): CatalogSnapshot;
|
|
222
|
+
/** Accept only a well-formed stored snapshot; anything else counts as no prior observation. */
|
|
223
|
+
export declare function parseCatalogSnapshot(value: unknown): CatalogSnapshot | undefined;
|
|
224
|
+
/** Compare the freshly read catalog against the previously observed one. */
|
|
225
|
+
export declare function diffCatalog(previous: CatalogSnapshot | undefined, next: CatalogSnapshot): CatalogDelta;
|
|
226
|
+
/** Compact, bounded catalog report. Detail lines are capped so the summary stays readable. */
|
|
227
|
+
export declare function formatCatalogDelta(delta: CatalogDelta): string;
|
|
228
|
+
export interface RoleRecommendation {
|
|
229
|
+
role: string;
|
|
230
|
+
selector: string;
|
|
231
|
+
rationale?: string;
|
|
232
|
+
}
|
|
233
|
+
export interface RoleRecommendationReview {
|
|
234
|
+
accepted: RoleRecommendation[];
|
|
235
|
+
/** Selectors rewritten to the catalog's own spelling, reported so the agent sees the repair. */
|
|
236
|
+
normalized: Array<{
|
|
237
|
+
role: string;
|
|
238
|
+
from: string;
|
|
239
|
+
to: string;
|
|
240
|
+
}>;
|
|
241
|
+
rejected: Array<{
|
|
242
|
+
role: string;
|
|
243
|
+
selector?: string;
|
|
244
|
+
reason: string;
|
|
245
|
+
}>;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Validate an agent-proposed distribution against the live catalog. A selector
|
|
249
|
+
* must name a catalog entry: only its casing is repaired (to the spelling the
|
|
250
|
+
* catalog reports, when that fold is unambiguous), and nothing is inferred from
|
|
251
|
+
* a model name. Unknown roles and unresolvable selectors are rejected rather
|
|
252
|
+
* than guessed.
|
|
253
|
+
*/
|
|
254
|
+
export declare function reviewRoleRecommendations(value: unknown, options: {
|
|
255
|
+
roles: readonly string[];
|
|
256
|
+
models: readonly AvailableModel[];
|
|
257
|
+
resolve?: (selector: string) => unknown;
|
|
258
|
+
}): RoleRecommendationReview;
|
|
259
|
+
/** Turn accepted recommendations into the same plan shape the local planner produces. */
|
|
260
|
+
export declare function planFromRecommendations(review: RoleRecommendationReview, models: readonly AvailableModel[]): RoleAssignmentPlan;
|
|
261
|
+
/** Before/after mapping limited to the Bizar roles, including explicit unchanged entries. */
|
|
262
|
+
export declare function formatRoleMapping(before: Record<string, unknown>, after: Record<string, unknown>, roles?: readonly string[]): string;
|
|
263
|
+
/** Settings surface a role commit writes to, plus the resolver that vets selectors. */
|
|
264
|
+
export interface RoleCommitInput {
|
|
265
|
+
settings: {
|
|
266
|
+
get(path: string): unknown;
|
|
267
|
+
set(path: string, value: unknown): void;
|
|
268
|
+
};
|
|
269
|
+
resolve(selector: string): unknown;
|
|
270
|
+
plan: RoleAssignmentPlan;
|
|
271
|
+
delta: CatalogDelta;
|
|
272
|
+
/** How the distribution was decided, reported back to the operator. */
|
|
273
|
+
source: string;
|
|
274
|
+
}
|
|
275
|
+
/** Present value as an object map; anything else is treated as empty. */
|
|
276
|
+
export declare function recordOf(value: unknown): Record<string, unknown>;
|
|
277
|
+
/**
|
|
278
|
+
* Resolve every proposed selector before writing anything, then persist the
|
|
279
|
+
* whole mapping in one `modelRoles` update. An unresolvable selector or an
|
|
280
|
+
* empty plan writes nothing at all, unrelated settings and non-Bizar roles are
|
|
281
|
+
* copied through untouched, and re-running against the current mapping is a
|
|
282
|
+
* no-op. The caller records the observed catalog only after this returns.
|
|
283
|
+
*/
|
|
284
|
+
export declare function commitRolePlan(input: RoleCommitInput): string;
|
|
285
|
+
/**
|
|
286
|
+
* Bounded evaluation request handed to the active agent. It carries only the
|
|
287
|
+
* live catalog, the current mapping and the deterministic baseline, so the
|
|
288
|
+
* agent can decide quickly without further research.
|
|
289
|
+
*/
|
|
290
|
+
export declare function buildAgentEvaluationRequest(input: {
|
|
291
|
+
models: readonly AvailableModel[];
|
|
292
|
+
currentRoles: Record<string, unknown>;
|
|
293
|
+
baseline: RoleAssignmentPlan;
|
|
294
|
+
delta: CatalogDelta;
|
|
295
|
+
}): string;
|
|
131
296
|
export {};
|
|
132
297
|
//# sourceMappingURL=model-role-assignment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-role-assignment.d.ts","sourceRoot":"","sources":["../../src/omp/model-role-assignment.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model-role-assignment.d.ts","sourceRoot":"","sources":["../../src/omp/model-role-assignment.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { ContractError } from "../core/errors.js";
|
|
1
3
|
/** Native-compatible defaults used until an operator runs model-role assignment. */
|
|
2
4
|
export const BIZAR_MODEL_ROLES = {
|
|
3
5
|
bizar_orchestrator: "@default",
|
|
@@ -54,6 +56,21 @@ export const BIZAR_MODEL_ROLE_AGENT_PROMPT = [
|
|
|
54
56
|
"Preserve non-Bizar roles and unrelated settings, make no project or credential changes, and report only a compact role-to-selector table plus unresolved roles.",
|
|
55
57
|
].join(" ");
|
|
56
58
|
const selectorFor = (model) => `${model.provider}/${model.id}`;
|
|
59
|
+
/**
|
|
60
|
+
* Catalog text comes from providers, so it is data, not instruction. Collapse
|
|
61
|
+
* every control character (including newlines, which would otherwise fabricate
|
|
62
|
+
* request lines) and cap the length of each provider-controlled value before it
|
|
63
|
+
* reaches the agent prompt.
|
|
64
|
+
*/
|
|
65
|
+
const sanitizeCatalogText = (value, limit = 96) => {
|
|
66
|
+
const collapsed = value.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
67
|
+
return collapsed.length > limit ? `${collapsed.slice(0, limit)}…` : collapsed;
|
|
68
|
+
};
|
|
69
|
+
/** Provider part of a `provider/model` selector, or `alias` for a bare native alias. */
|
|
70
|
+
const selectorProvider = (selector) => {
|
|
71
|
+
const separator = selector.indexOf("/");
|
|
72
|
+
return separator > 0 ? selector.slice(0, separator) : "alias";
|
|
73
|
+
};
|
|
57
74
|
const hasTextInput = (model) => !model.input || model.input.length === 0 || model.input.includes("text");
|
|
58
75
|
const THINKING_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
59
76
|
function thinkingLevels(value) {
|
|
@@ -101,7 +118,10 @@ function qualityScore(model, profile) {
|
|
|
101
118
|
const output = Math.min(1, Math.log10(Math.max(1, model.maxTokens ?? 0)) / 6);
|
|
102
119
|
const reasoning = model.reasoning ? 1 : 0;
|
|
103
120
|
const thinking = maxThinkingRank(model) / 5;
|
|
104
|
-
|
|
121
|
+
// Unknown pricing is scored at the middle of the penalty range, never at the
|
|
122
|
+
// cheap end: a model whose cost metadata is missing must not win a
|
|
123
|
+
// cost-sensitive role by omission, and it is reported as `cost: unknown`.
|
|
124
|
+
const costPenalty = modelCostKnown(model) ? Math.min(1, Math.log10(1 + hourlyCost(model)) / 3) : 0.5;
|
|
105
125
|
// A role may prefer locally hosted models (free per token, on-device).
|
|
106
126
|
// The bonus is additive and opt-in per profile: a local model that fits
|
|
107
127
|
// overtakes comparable cloud models, while a mismatch still falls back.
|
|
@@ -149,9 +169,12 @@ export function planBizarModelRoles(query) {
|
|
|
149
169
|
return { assignments, unavailable, modelCount: models.length };
|
|
150
170
|
}
|
|
151
171
|
function modelCostKnown(model) { return typeof model.cost?.input === "number" && typeof model.cost?.output === "number"; }
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Pure mapping copy: applies a plan onto a settings map without mutating or
|
|
174
|
+
* validating it. Staleness is checked by the caller that owns the write, so no
|
|
175
|
+
* caller can pass a hash of the same object it is about to overwrite.
|
|
176
|
+
*/
|
|
177
|
+
export function applyRoleAssignmentPlan(current, plan) {
|
|
155
178
|
const next = { ...current };
|
|
156
179
|
for (const assignment of plan.assignments)
|
|
157
180
|
next[assignment.role] = assignment.selector;
|
|
@@ -167,4 +190,370 @@ export function formatRoleAssignmentPlan(plan) {
|
|
|
167
190
|
lines.push("No settings were changed because the plan is incomplete.");
|
|
168
191
|
return lines.join("\n");
|
|
169
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Native tool the agent calls to persist a reviewed distribution. The command
|
|
195
|
+
* handler cannot make a model call itself, so `/bizar-models` gathers the
|
|
196
|
+
* catalog, then hands the bounded evaluation to the active agent, which must
|
|
197
|
+
* call this tool for anything to be written.
|
|
198
|
+
*/
|
|
199
|
+
export const BIZAR_MODEL_ROLE_TOOL = "bizar_apply_model_roles";
|
|
200
|
+
/**
|
|
201
|
+
* Longest operator notice OMP's status line can carry without clipping.
|
|
202
|
+
*
|
|
203
|
+
* `ctx.ui.notify(message, "info")` renders through the host status line, which
|
|
204
|
+
* builds exactly one text component and folds it onto two screen lines at the
|
|
205
|
+
* default 80-column terminal width — roughly 300 characters. The model-role
|
|
206
|
+
* command's own report (catalog movement, headline, and a line per role) runs
|
|
207
|
+
* far past that, so the tail silently never appears. A `warning`/`error` notice
|
|
208
|
+
* is not an alternative: those draw the full error banner.
|
|
209
|
+
*/
|
|
210
|
+
export const OPERATOR_NOTICE_LIMIT = 300;
|
|
211
|
+
/**
|
|
212
|
+
* Fit a report into the status line, clipping from the front. Catalog movement
|
|
213
|
+
* and the applied-map verdict sit before the per-role lines, so a truncated
|
|
214
|
+
* notice always keeps the lines that say what happened and loses the tail.
|
|
215
|
+
*/
|
|
216
|
+
export function clipOperatorNotice(report, limit = OPERATOR_NOTICE_LIMIT) {
|
|
217
|
+
const text = report.trim();
|
|
218
|
+
if (text.length <= limit)
|
|
219
|
+
return text;
|
|
220
|
+
const dropped = text.length - limit;
|
|
221
|
+
const note = `…clipped; ${dropped} more characters in the session log`;
|
|
222
|
+
if (note.length >= limit)
|
|
223
|
+
return note.slice(-limit);
|
|
224
|
+
const head = text.slice(0, limit - note.length - 1).trimEnd();
|
|
225
|
+
return `${head} ${note}`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Decide how this host can distribute roles. An agent evaluation needs both the
|
|
229
|
+
* live catalog and a way to reach the agent; a host without agent messaging
|
|
230
|
+
* falls back to deterministic scoring; a host without any of it cannot assign
|
|
231
|
+
* roles and says so instead of writing a partial mapping.
|
|
232
|
+
*/
|
|
233
|
+
export function roleAssignmentRoute(host) {
|
|
234
|
+
const settingsHost = host.settings;
|
|
235
|
+
const get = settingsHost?.get?.bind(settingsHost);
|
|
236
|
+
const set = settingsHost?.set?.bind(settingsHost);
|
|
237
|
+
const sendMessage = host.sendUserMessage;
|
|
238
|
+
// OMP queues an explicit `followUp` without starting a turn, and only
|
|
239
|
+
// auto-continues it when the transcript tail is already assistant/toolResult:
|
|
240
|
+
// in a session that has not run a turn yet the evaluation stays queued behind
|
|
241
|
+
// the user's next prompt instead of running. `aside` starts a turn at an idle
|
|
242
|
+
// session, and while streaming it is injected at the next step boundary
|
|
243
|
+
// instead of aborting the in-flight tool batch.
|
|
244
|
+
const send = sendMessage ? (content) => { sendMessage(content, { deliverAs: "aside" }); } : undefined;
|
|
245
|
+
const models = host.models ? [...host.models] : [];
|
|
246
|
+
if (get && set && models.length > 0) {
|
|
247
|
+
const settings = { get, set };
|
|
248
|
+
return send ? { kind: "agent", settings, models, send } : { kind: "local-plan", settings, models };
|
|
249
|
+
}
|
|
250
|
+
return send ? { kind: "agent-prompt-only", send } : { kind: "unsupported" };
|
|
251
|
+
}
|
|
252
|
+
function catalogFields(model) {
|
|
253
|
+
const levels = thinkingLevels(model.thinking);
|
|
254
|
+
return {
|
|
255
|
+
provider: model.provider,
|
|
256
|
+
contextWindow: typeof model.contextWindow === "number" ? String(model.contextWindow) : "unknown",
|
|
257
|
+
maxTokens: typeof model.maxTokens === "number" ? String(model.maxTokens) : "unknown",
|
|
258
|
+
reasoning: model.reasoning === true ? "yes" : model.reasoning === false ? "no" : "unknown",
|
|
259
|
+
thinking: levels.length > 0 ? levels.join(",") : "unknown",
|
|
260
|
+
cost: modelCostKnown(model) ? `${model.cost?.input}/${model.cost?.output}` : "unknown",
|
|
261
|
+
input: model.input && model.input.length > 0 ? [...model.input].sort().join(",") : "unknown",
|
|
262
|
+
locality: isLocalModel(model) ? "local" : "remote",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const describeFields = (fields) => Object.keys(fields).sort().map(key => `${key}=${sanitizeCatalogText(fields[key], 64)}`).join(" ");
|
|
266
|
+
function describeFieldChanges(prior, next) {
|
|
267
|
+
return Object.keys(next).sort().flatMap(key => {
|
|
268
|
+
const before = prior[key] ?? "unknown";
|
|
269
|
+
return before === next[key] ? [] : [`${key} ${before} -> ${next[key]}`];
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/** Normalize a catalog into a comparable, digestible observation. */
|
|
273
|
+
export function snapshotCatalog(models, capturedAt = Date.now()) {
|
|
274
|
+
const records = models
|
|
275
|
+
.map(model => ({ selector: selectorFor(model), fields: catalogFields(model) }))
|
|
276
|
+
.sort((left, right) => left.selector.localeCompare(right.selector));
|
|
277
|
+
const lines = records.map(({ selector, fields }) => `${selector}|${describeFields(fields)}`);
|
|
278
|
+
return { digest: createHash("sha256").update(lines.join("\n")).digest("hex"), capturedAt, models: records };
|
|
279
|
+
}
|
|
280
|
+
/** Accept only a well-formed stored snapshot; anything else counts as no prior observation. */
|
|
281
|
+
export function parseCatalogSnapshot(value) {
|
|
282
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
283
|
+
return undefined;
|
|
284
|
+
const record = value;
|
|
285
|
+
if (typeof record.digest !== "string" || !Array.isArray(record.models))
|
|
286
|
+
return undefined;
|
|
287
|
+
const models = [];
|
|
288
|
+
for (const entry of record.models) {
|
|
289
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
290
|
+
return undefined;
|
|
291
|
+
const model = entry;
|
|
292
|
+
if (typeof model.selector !== "string" || !model.fields || typeof model.fields !== "object" || Array.isArray(model.fields))
|
|
293
|
+
return undefined;
|
|
294
|
+
const fields = {};
|
|
295
|
+
for (const [key, field] of Object.entries(model.fields)) {
|
|
296
|
+
if (typeof field !== "string")
|
|
297
|
+
return undefined;
|
|
298
|
+
fields[key] = field;
|
|
299
|
+
}
|
|
300
|
+
models.push({ selector: model.selector, fields });
|
|
301
|
+
}
|
|
302
|
+
return { digest: record.digest, capturedAt: typeof record.capturedAt === "number" ? record.capturedAt : 0, models };
|
|
303
|
+
}
|
|
304
|
+
/** Compare the freshly read catalog against the previously observed one. */
|
|
305
|
+
export function diffCatalog(previous, next) {
|
|
306
|
+
const before = new Map((previous?.models ?? []).map(model => [model.selector, model.fields]));
|
|
307
|
+
const after = new Map(next.models.map(model => [model.selector, model.fields]));
|
|
308
|
+
const added = [];
|
|
309
|
+
const removed = [];
|
|
310
|
+
const changed = [];
|
|
311
|
+
let unchanged = 0;
|
|
312
|
+
for (const [selector, fields] of after) {
|
|
313
|
+
const prior = before.get(selector);
|
|
314
|
+
if (!prior) {
|
|
315
|
+
added.push({ kind: "added", selector, detail: describeFields(fields) });
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const differences = describeFieldChanges(prior, fields);
|
|
319
|
+
if (differences.length > 0)
|
|
320
|
+
changed.push({ kind: "changed", selector, detail: differences.join("; ") });
|
|
321
|
+
else
|
|
322
|
+
unchanged += 1;
|
|
323
|
+
}
|
|
324
|
+
for (const selector of before.keys()) {
|
|
325
|
+
if (!after.has(selector))
|
|
326
|
+
removed.push({ kind: "removed", selector, detail: "no longer returned by the authenticated catalog" });
|
|
327
|
+
}
|
|
328
|
+
return { firstObservation: previous === undefined, added, removed, changed, unchanged, modelCount: next.models.length };
|
|
329
|
+
}
|
|
330
|
+
/** Compact, bounded catalog report. Detail lines are capped so the summary stays readable. */
|
|
331
|
+
export function formatCatalogDelta(delta) {
|
|
332
|
+
if (delta.firstObservation)
|
|
333
|
+
return `Catalog: first observation, ${delta.modelCount} available models recorded.`;
|
|
334
|
+
const headline = `Catalog: ${delta.added.length} added, ${delta.removed.length} removed, ${delta.changed.length} changed, ${delta.unchanged} unchanged (${delta.modelCount} available).`;
|
|
335
|
+
const all = [...delta.added, ...delta.removed, ...delta.changed];
|
|
336
|
+
const details = all
|
|
337
|
+
.slice(0, 8)
|
|
338
|
+
.map(change => ` ${change.kind === "added" ? "+" : change.kind === "removed" ? "-" : "~"} ${change.selector}: ${change.detail}`);
|
|
339
|
+
if (all.length > details.length)
|
|
340
|
+
details.push(` … ${all.length - details.length} more changes not shown`);
|
|
341
|
+
return [headline, ...details].join("\n");
|
|
342
|
+
}
|
|
343
|
+
const RECOMMENDATION_REJECTED = "expected {\"assignment\":[{\"role\":..,\"selector\":..}]} or a role-to-selector object";
|
|
344
|
+
function recommendationCandidates(value) {
|
|
345
|
+
if (Array.isArray(value))
|
|
346
|
+
return value;
|
|
347
|
+
if (!value || typeof value !== "object")
|
|
348
|
+
return undefined;
|
|
349
|
+
const record = value;
|
|
350
|
+
if (Array.isArray(record.assignment))
|
|
351
|
+
return record.assignment;
|
|
352
|
+
if (Array.isArray(record.assignments))
|
|
353
|
+
return record.assignments;
|
|
354
|
+
if (record.roles && typeof record.roles === "object" && !Array.isArray(record.roles)) {
|
|
355
|
+
return Object.entries(record.roles).map(([role, selector]) => ({ role, selector }));
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Catalog selectors keyed by case-folded spelling. A fold shared by two catalog
|
|
361
|
+
* entries is dropped, so a repair never picks one of two live models.
|
|
362
|
+
*/
|
|
363
|
+
function caseFoldedSelectors(selectors) {
|
|
364
|
+
const folded = new Map();
|
|
365
|
+
const ambiguous = new Set();
|
|
366
|
+
for (const selector of selectors) {
|
|
367
|
+
const key = selector.toLowerCase();
|
|
368
|
+
if (ambiguous.has(key))
|
|
369
|
+
continue;
|
|
370
|
+
if (folded.has(key)) {
|
|
371
|
+
folded.delete(key);
|
|
372
|
+
ambiguous.add(key);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
folded.set(key, selector);
|
|
376
|
+
}
|
|
377
|
+
return folded;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Validate an agent-proposed distribution against the live catalog. A selector
|
|
381
|
+
* must name a catalog entry: only its casing is repaired (to the spelling the
|
|
382
|
+
* catalog reports, when that fold is unambiguous), and nothing is inferred from
|
|
383
|
+
* a model name. Unknown roles and unresolvable selectors are rejected rather
|
|
384
|
+
* than guessed.
|
|
385
|
+
*/
|
|
386
|
+
export function reviewRoleRecommendations(value, options) {
|
|
387
|
+
const candidates = recommendationCandidates(value);
|
|
388
|
+
if (!candidates)
|
|
389
|
+
return { accepted: [], normalized: [], rejected: [{ role: "*", reason: RECOMMENDATION_REJECTED }] };
|
|
390
|
+
const known = new Set(options.roles);
|
|
391
|
+
const selectors = new Set(options.models.map(model => selectorFor(model)));
|
|
392
|
+
const folded = caseFoldedSelectors(selectors);
|
|
393
|
+
const accepted = [];
|
|
394
|
+
const normalized = [];
|
|
395
|
+
const rejected = [];
|
|
396
|
+
const seen = new Set();
|
|
397
|
+
for (const candidate of candidates) {
|
|
398
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
399
|
+
rejected.push({ role: "*", reason: "each recommendation must be an object with role and selector" });
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const entry = candidate;
|
|
403
|
+
const role = typeof entry.role === "string" ? entry.role.trim() : "";
|
|
404
|
+
const selector = typeof entry.selector === "string" ? entry.selector.trim() : "";
|
|
405
|
+
if (!role || !selector) {
|
|
406
|
+
rejected.push({ role: role || "*", selector: selector || undefined, reason: "each recommendation needs both a role and a selector" });
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (!known.has(role)) {
|
|
410
|
+
rejected.push({ role: role || "*", selector: selector || undefined, reason: "not a Bizar model role" });
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (seen.has(role)) {
|
|
414
|
+
rejected.push({ role, selector, reason: "duplicate recommendation for this role" });
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (selectors.size === 0 && !options.resolve) {
|
|
418
|
+
rejected.push({ role, selector: selector || undefined, reason: "the host exposed no model catalog to validate against" });
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const catalogSelector = selectors.has(selector) ? selector : folded.get(selector.toLowerCase());
|
|
422
|
+
if (catalogSelector === undefined && !options.resolve?.(selector)) {
|
|
423
|
+
rejected.push({
|
|
424
|
+
role,
|
|
425
|
+
selector,
|
|
426
|
+
reason: options.resolve ? "selector is not resolvable in the live catalog or by an OMP model alias" : "selector is not in the live catalog",
|
|
427
|
+
});
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
seen.add(role);
|
|
431
|
+
const rationale = typeof entry.rationale === "string" && entry.rationale.trim() ? entry.rationale.trim() : undefined;
|
|
432
|
+
if (catalogSelector !== undefined && catalogSelector !== selector)
|
|
433
|
+
normalized.push({ role, from: selector, to: catalogSelector });
|
|
434
|
+
accepted.push({ role, selector: catalogSelector ?? selector, rationale });
|
|
435
|
+
}
|
|
436
|
+
return { accepted, normalized, rejected };
|
|
437
|
+
}
|
|
438
|
+
/** Turn accepted recommendations into the same plan shape the local planner produces. */
|
|
439
|
+
export function planFromRecommendations(review, models) {
|
|
440
|
+
const bySelector = new Map(models.map(model => [selectorFor(model), model]));
|
|
441
|
+
const assignments = [];
|
|
442
|
+
for (const recommendation of review.accepted) {
|
|
443
|
+
// A selector the review accepted through the host resolver (a native alias
|
|
444
|
+
// such as `@smol`, or an effort suffix) has no catalog record; it is carried
|
|
445
|
+
// through with unknown metadata instead of being dropped here, so the write
|
|
446
|
+
// gate stays the single place that decides resolvability.
|
|
447
|
+
const model = bySelector.get(recommendation.selector) ?? { provider: selectorProvider(recommendation.selector), id: recommendation.selector };
|
|
448
|
+
assignments.push({
|
|
449
|
+
role: recommendation.role,
|
|
450
|
+
selector: recommendation.selector,
|
|
451
|
+
model,
|
|
452
|
+
rationale: recommendation.rationale ?? "agent-selected distribution",
|
|
453
|
+
alternatives: [],
|
|
454
|
+
metadata: { cost: modelCostKnown(model) ? "known" : "unknown", locality: isLocalModel(model) ? "provider-hint" : "unknown" },
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
assignments,
|
|
459
|
+
unavailable: review.rejected.map(item => ({ role: item.role, reason: item.reason })),
|
|
460
|
+
modelCount: models.length,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/** Before/after mapping limited to the Bizar roles, including explicit unchanged entries. */
|
|
464
|
+
export function formatRoleMapping(before, after, roles = Object.keys(BIZAR_MODEL_ROLES)) {
|
|
465
|
+
return roles.map(role => {
|
|
466
|
+
const prior = typeof before[role] === "string" ? before[role] : "(unset)";
|
|
467
|
+
const next = typeof after[role] === "string" ? after[role] : "(unset)";
|
|
468
|
+
if (prior === next)
|
|
469
|
+
return `- ${role}: ${next} (unchanged)`;
|
|
470
|
+
return `- ${role}: ${prior} -> ${next}`;
|
|
471
|
+
}).join("\n");
|
|
472
|
+
}
|
|
473
|
+
/** Present value as an object map; anything else is treated as empty. */
|
|
474
|
+
export function recordOf(value) {
|
|
475
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Resolve every proposed selector before writing anything, then persist the
|
|
479
|
+
* whole mapping in one `modelRoles` update. An unresolvable selector or an
|
|
480
|
+
* empty plan writes nothing at all, unrelated settings and non-Bizar roles are
|
|
481
|
+
* copied through untouched, and re-running against the current mapping is a
|
|
482
|
+
* no-op. The caller records the observed catalog only after this returns.
|
|
483
|
+
*/
|
|
484
|
+
export function commitRolePlan(input) {
|
|
485
|
+
const roles = Object.keys(BIZAR_MODEL_ROLES);
|
|
486
|
+
const before = recordOf(input.settings.get("modelRoles"));
|
|
487
|
+
if (input.plan.assignments.length === 0) {
|
|
488
|
+
const reasons = input.plan.unavailable.map(({ role, reason }) => `${role}: ${reason}`).join("; ");
|
|
489
|
+
throw new ContractError(`Bizar model mapping skipped: no Bizar role had an eligible catalog candidate.${reasons ? ` ${reasons}` : ""}`);
|
|
490
|
+
}
|
|
491
|
+
const unresolved = input.plan.assignments.filter(({ selector }) => !input.resolve(selector));
|
|
492
|
+
if (unresolved.length > 0) {
|
|
493
|
+
throw new ContractError(`Bizar model mapping refused; OMP could not resolve ${unresolved.map(({ role, selector }) => `${role}=${selector}`).join(", ")}. No settings were written.`);
|
|
494
|
+
}
|
|
495
|
+
// Re-read immediately before writing: selector resolution and the plan were
|
|
496
|
+
// computed against the mapping read above, so a concurrent edit in that window
|
|
497
|
+
// must refuse the commit instead of being overwritten.
|
|
498
|
+
const current = recordOf(input.settings.get("modelRoles"));
|
|
499
|
+
if (JSON.stringify(current) !== JSON.stringify(before)) {
|
|
500
|
+
throw new ContractError("Bizar model-role settings changed while the distribution was being resolved; nothing was written. Re-run /bizar-models.");
|
|
501
|
+
}
|
|
502
|
+
const after = applyRoleAssignmentPlan(before, input.plan);
|
|
503
|
+
const changed = JSON.stringify(after) !== JSON.stringify(before);
|
|
504
|
+
if (changed)
|
|
505
|
+
input.settings.set("modelRoles", after);
|
|
506
|
+
const covered = new Set(input.plan.assignments.map(({ role }) => role));
|
|
507
|
+
const uncovered = roles.filter(role => !covered.has(role));
|
|
508
|
+
const lines = [
|
|
509
|
+
formatCatalogDelta(input.delta),
|
|
510
|
+
`Assigned ${input.plan.assignments.length}/${roles.length} Bizar roles from ${input.plan.modelCount} available OMP models (${input.source}); modelRoles ${changed ? "updated" : "already current, left as is"}.`,
|
|
511
|
+
formatRoleMapping(before, after, roles),
|
|
512
|
+
];
|
|
513
|
+
if (uncovered.length > 0)
|
|
514
|
+
lines.push(`Left unchanged (not assigned by this evaluation): ${uncovered.join(", ")}.`);
|
|
515
|
+
if (input.plan.unavailable.length > 0)
|
|
516
|
+
lines.push(`Not applied: ${input.plan.unavailable.map(({ role, reason }) => `${role} (${reason})`).join("; ")}.`);
|
|
517
|
+
return lines.join("\n");
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Bounded evaluation request handed to the active agent. It carries only the
|
|
521
|
+
* live catalog, the current mapping and the deterministic baseline, so the
|
|
522
|
+
* agent can decide quickly without further research.
|
|
523
|
+
*/
|
|
524
|
+
export function buildAgentEvaluationRequest(input) {
|
|
525
|
+
const catalogLines = input.models
|
|
526
|
+
.map(model => `${sanitizeCatalogText(selectorFor(model))} ${describeFields(catalogFields(model))}`)
|
|
527
|
+
.sort();
|
|
528
|
+
const currentLines = BIZAR_ROLE_PROFILES.map(profile => {
|
|
529
|
+
const selector = typeof input.currentRoles[profile.role] === "string" ? sanitizeCatalogText(input.currentRoles[profile.role]) : "(unset)";
|
|
530
|
+
return `${profile.role} = ${selector} :: ${profile.purpose}; ${profile.preference} preference; ${profile.requiresReasoning ? "reasoning required" : "reasoning optional"}; context >= ${profile.minContextWindow}${profile.preferDistinctFrom ? `; prefer distinct from ${profile.preferDistinctFrom}` : ""}`;
|
|
531
|
+
});
|
|
532
|
+
const baselineLines = input.baseline.assignments.map(assignment => `${assignment.role} = ${assignment.selector}`);
|
|
533
|
+
const missing = input.baseline.unavailable.map(item => `${item.role} (${item.reason})`);
|
|
534
|
+
return [
|
|
535
|
+
"Evaluate the Bizar model-role distribution for the current OMP catalog and apply it with the native tool `" + BIZAR_MODEL_ROLE_TOOL + "`.",
|
|
536
|
+
"This is a bounded evaluation: use only the catalog below, make no other settings or credential changes, and do not run further research.",
|
|
537
|
+
"",
|
|
538
|
+
"Rules",
|
|
539
|
+
`- Call \`${BIZAR_MODEL_ROLE_TOOL}\` exactly once with {"assignment":[{"role":"...","selector":"...","rationale":"..."}]} covering every Bizar role you consider resolved.`,
|
|
540
|
+
"- Use selectors exactly as listed in AVAILABLE MODELS; never invent or guess a selector.",
|
|
541
|
+
"- Treat metadata reported as unknown as unknown: never infer capability, cost or locality from a model name.",
|
|
542
|
+
"- Judge each role by purpose, reasoning need, context window, speed, cost and locality; keep provider diversity, and prefer a model distinct from the orchestrator for bizar_advisor.",
|
|
543
|
+
"- One short clause per rationale, no prose report.",
|
|
544
|
+
"",
|
|
545
|
+
"CATALOG CHANGES SINCE LAST OBSERVATION",
|
|
546
|
+
formatCatalogDelta(input.delta),
|
|
547
|
+
"",
|
|
548
|
+
`AVAILABLE MODELS (${input.models.length})`,
|
|
549
|
+
...catalogLines,
|
|
550
|
+
"",
|
|
551
|
+
"CURRENT ROLE MAPPING (existing assignments are re-evaluated, not trusted)",
|
|
552
|
+
...currentLines,
|
|
553
|
+
"",
|
|
554
|
+
"DETERMINISTIC BASELINE (override only with a concrete catalog reason)",
|
|
555
|
+
...baselineLines,
|
|
556
|
+
...(missing.length > 0 ? [`Unresolved by the baseline: ${missing.join(", ")}`] : []),
|
|
557
|
+
].join("\n");
|
|
558
|
+
}
|
|
170
559
|
//# sourceMappingURL=model-role-assignment.js.map
|