@askalf/dario 5.5.5 → 5.5.7

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.
@@ -114,9 +114,15 @@ export declare const CC_SYSTEM_PROMPT_OPUS5: string;
114
114
  */
115
115
  export declare const CC_SYSTEM_PROMPT_SONNET5: string;
116
116
  /**
117
- * The system prompt CC would send for `model`: Fable-family gets the Fable
118
- * variant, every other model gets the shared base. Keeps dario byte-aligned with
119
- * CC's per-model system prompt (dario#lock-step). Mirrors betaForModel's shape.
117
+ * The system prompt CC would send for `model`: a family in VARIANT_FAMILIES
118
+ * gets its captured variant, every other model gets the shared base. Keeps
119
+ * dario byte-aligned with CC's per-model system prompt (dario#lock-step).
120
+ *
121
+ * Selection iterates VARIANT_FAMILIES (live-fingerprint.ts) — the same table
122
+ * the bake captures from — so the two sides cannot drift apart. A family
123
+ * whose variant is absent from the loaded template falls back to the base;
124
+ * `dario doctor`'s "Prompt variants" row surfaces that state instead of
125
+ * letting it pass silently.
120
126
  */
121
127
  export declare function systemPromptForModel(model?: string): string;
122
128
  /** CC's agent identity string. */
@@ -7,7 +7,7 @@
7
7
  * live cache self-heals when Anthropic ships a new CC version — no user
8
8
  * action required. See src/live-fingerprint.ts for the capture pipeline.
9
9
  */
10
- import { loadTemplate, promptVariantsOf } from './live-fingerprint.js';
10
+ import { loadTemplate, promptVariantsOf, VARIANT_FAMILIES } from './live-fingerprint.js';
11
11
  // Load template at module init — prefer live cache, fall back to bundled.
12
12
  const TEMPLATE = loadTemplate({ silent: true });
13
13
  /** The loaded template itself — source, version, capture age, all fields. Startup banners and drift checks read this directly. */
@@ -130,20 +130,22 @@ export const CC_SYSTEM_PROMPT_OPUS5 = _variants['opus-5'] ?? CC_SYSTEM_PROMPT;
130
130
  */
131
131
  export const CC_SYSTEM_PROMPT_SONNET5 = _variants['sonnet-5'] ?? CC_SYSTEM_PROMPT;
132
132
  /**
133
- * The system prompt CC would send for `model`: Fable-family gets the Fable
134
- * variant, every other model gets the shared base. Keeps dario byte-aligned with
135
- * CC's per-model system prompt (dario#lock-step). Mirrors betaForModel's shape.
133
+ * The system prompt CC would send for `model`: a family in VARIANT_FAMILIES
134
+ * gets its captured variant, every other model gets the shared base. Keeps
135
+ * dario byte-aligned with CC's per-model system prompt (dario#lock-step).
136
+ *
137
+ * Selection iterates VARIANT_FAMILIES (live-fingerprint.ts) — the same table
138
+ * the bake captures from — so the two sides cannot drift apart. A family
139
+ * whose variant is absent from the loaded template falls back to the base;
140
+ * `dario doctor`'s "Prompt variants" row surfaces that state instead of
141
+ * letting it pass silently.
136
142
  */
137
143
  export function systemPromptForModel(model) {
138
144
  const m = (model ?? '').toLowerCase();
139
- if (m.includes('fable'))
140
- return CC_SYSTEM_PROMPT_FABLE;
141
- // `-5` bounded so a future opus-50 doesn't match, and so the `[1m]` tag
142
- // (which is not a digit) still does.
143
- if (/opus-5(?!\d)/.test(m))
144
- return CC_SYSTEM_PROMPT_OPUS5;
145
- if (/sonnet-5(?!\d)/.test(m))
146
- return CC_SYSTEM_PROMPT_SONNET5;
145
+ for (const f of VARIANT_FAMILIES) {
146
+ if (f.matches(m))
147
+ return _variants[f.key] ?? CC_SYSTEM_PROMPT;
148
+ }
147
149
  return CC_SYSTEM_PROMPT;
148
150
  }
149
151
  /** CC's agent identity string. */
package/dist/doctor.js CHANGED
@@ -17,7 +17,7 @@ import { homedir, platform, arch, release } from 'node:os';
17
17
  import { execFileSync } from 'node:child_process';
18
18
  import { createServer } from 'node:http';
19
19
  import { CC_TEMPLATE, resolveSystemPrompt, } from './cc-template.js';
20
- import { describeTemplate, detectDrift, checkCCCompat, findInstalledCC, SUPPORTED_CC_RANGE, CURRENT_SCHEMA_VERSION, compareVersions, } from './live-fingerprint.js';
20
+ import { describeTemplate, detectDrift, checkCCCompat, findInstalledCC, missingVariantFamilies, SUPPORTED_CC_RANGE, CURRENT_SCHEMA_VERSION, compareVersions, VARIANT_FAMILIES, } from './live-fingerprint.js';
21
21
  import { detectCCOAuthConfig } from './cc-oauth-detect.js';
22
22
  import { runAuthorizeProbe } from './cc-authorize-probe.js';
23
23
  import { MIGRATED_LOGIN_ALIAS } from './accounts.js';
@@ -352,6 +352,32 @@ export async function runChecks(opts = {}) {
352
352
  catch (err) {
353
353
  checks.push({ status: 'fail', label: 'Template', detail: `load failed: ${err.message}` });
354
354
  }
355
+ // ---- Per-model prompt variants (dario#lock-step)
356
+ // CC ships several model families a different system prompt than the shared
357
+ // base. A template missing a family's variant silently serves that family
358
+ // the base prompt — a wire-fidelity degradation with no other symptom
359
+ // (requests still 200). Known causes: a bundle baked while variant capture
360
+ // failed, or a pre-variants live cache shadowing the bundle.
361
+ try {
362
+ const missing = missingVariantFamilies(CC_TEMPLATE);
363
+ if (missing.length === 0) {
364
+ checks.push({
365
+ status: 'ok',
366
+ label: 'Prompt variants',
367
+ detail: `all ${VARIANT_FAMILIES.length} model families carried (${VARIANT_FAMILIES.map((f) => f.key).join(', ')})`,
368
+ });
369
+ }
370
+ else {
371
+ checks.push({
372
+ status: 'warn',
373
+ label: 'Prompt variants',
374
+ detail: `missing: ${missing.join(', ')} — those models get the shared base prompt, not CC's model-specific one. Re-bake the template, or remove a pre-variants live cache (~/.dario/cc-template.live.json) and restart.`,
375
+ });
376
+ }
377
+ }
378
+ catch (err) {
379
+ checks.push({ status: 'warn', label: 'Prompt variants', detail: `check failed: ${err.message}` });
380
+ }
355
381
  // ---- Per-request overhead surfacing.
356
382
  // The CC system prompt + tool definitions are injected into every
357
383
  // non-passthrough request and dominate the input-token cost on small
@@ -181,6 +181,42 @@ export interface TemplateData {
181
181
  * paths cannot drift apart.
182
182
  */
183
183
  export declare const TEMPLATE_BASE_MODEL = "claude-opus-4-8";
184
+ /**
185
+ * One model family CC ships a distinct system prompt to (dario#lock-step).
186
+ */
187
+ export interface VariantFamily {
188
+ /** Key into `system_prompt_variants`. */
189
+ key: string;
190
+ /** Model id the bake pins (via ANTHROPIC_MODEL) to capture this family. */
191
+ captureModel: string;
192
+ /** True when a lowercased model id belongs to this family. */
193
+ matches: (model: string) => boolean;
194
+ }
195
+ /**
196
+ * The model families CC ships a DIFFERENT system prompt than the shared
197
+ * base, in matcher-precedence order (first match wins — fable stays ahead
198
+ * of the `-5` arms so a hypothetical fable-5 never falls into them).
199
+ *
200
+ * This table is the single source of truth for dario#lock-step: the
201
+ * runtime selection (cc-template.ts:systemPromptForModel), the bake's
202
+ * capture loop (scripts/capture-and-bake.mjs), the doctor's coverage row,
203
+ * and the template invariants all derive from it — the same reason
204
+ * TEMPLATE_BASE_MODEL lives here. Before this table, the bake's model
205
+ * list and the selection arms were maintained by hand in two files, the
206
+ * exact divergence class that shipped tool_names ≠ tools twice.
207
+ *
208
+ * Adding a family here is safe even if it turns out to share the base
209
+ * prompt: the bake stores a variant only when the capture differs.
210
+ */
211
+ export declare const VARIANT_FAMILIES: readonly VariantFamily[];
212
+ /**
213
+ * Families from VARIANT_FAMILIES that `t` carries no variant for — i.e.
214
+ * models that would silently get the shared BASE prompt instead of CC's
215
+ * model-specific one. Non-empty on a healthy current bundle means the
216
+ * lock-step is degraded: a bad bake, an unreadable bundle behind a live
217
+ * capture, or a pre-variants live cache shadowing the bundle.
218
+ */
219
+ export declare function missingVariantFamilies(t: TemplateData): string[];
184
220
  /**
185
221
  * A template's prompt variants, with the legacy `system_prompt_fable` slot
186
222
  * folded in under the `fable` key. Callers should always go through this
@@ -109,6 +109,41 @@ export const CURRENT_SCHEMA_VERSION = 3;
109
109
  * paths cannot drift apart.
110
110
  */
111
111
  export const TEMPLATE_BASE_MODEL = 'claude-opus-4-8';
112
+ /**
113
+ * The model families CC ships a DIFFERENT system prompt than the shared
114
+ * base, in matcher-precedence order (first match wins — fable stays ahead
115
+ * of the `-5` arms so a hypothetical fable-5 never falls into them).
116
+ *
117
+ * This table is the single source of truth for dario#lock-step: the
118
+ * runtime selection (cc-template.ts:systemPromptForModel), the bake's
119
+ * capture loop (scripts/capture-and-bake.mjs), the doctor's coverage row,
120
+ * and the template invariants all derive from it — the same reason
121
+ * TEMPLATE_BASE_MODEL lives here. Before this table, the bake's model
122
+ * list and the selection arms were maintained by hand in two files, the
123
+ * exact divergence class that shipped tool_names ≠ tools twice.
124
+ *
125
+ * Adding a family here is safe even if it turns out to share the base
126
+ * prompt: the bake stores a variant only when the capture differs.
127
+ */
128
+ export const VARIANT_FAMILIES = [
129
+ { key: 'fable', captureModel: 'claude-fable-5', matches: (m) => m.includes('fable') },
130
+ // `-5` bounded so a future opus-50 doesn't match, and so the `[1m]` tag
131
+ // (which is not a digit) still does.
132
+ { key: 'opus-5', captureModel: 'claude-opus-5', matches: (m) => /opus-5(?!\d)/.test(m) },
133
+ { key: 'sonnet-5', captureModel: 'claude-sonnet-5', matches: (m) => /sonnet-5(?!\d)/.test(m) },
134
+ ];
135
+ /**
136
+ * Families from VARIANT_FAMILIES that `t` carries no variant for — i.e.
137
+ * models that would silently get the shared BASE prompt instead of CC's
138
+ * model-specific one. Non-empty on a healthy current bundle means the
139
+ * lock-step is degraded: a bad bake, an unreadable bundle behind a live
140
+ * capture, or a pre-variants live cache shadowing the bundle.
141
+ */
142
+ export function missingVariantFamilies(t) {
143
+ const have = promptVariantsOf(t);
144
+ return VARIANT_FAMILIES.filter((f) => !(typeof have[f.key] === 'string' && have[f.key].length > 0))
145
+ .map((f) => f.key);
146
+ }
112
147
  /**
113
148
  * A template's prompt variants, with the legacy `system_prompt_fable` slot
114
149
  * folded in under the `fable` key. Callers should always go through this
@@ -913,7 +948,12 @@ export function formatCaptureAge(capturedIso, now = Date.now()) {
913
948
  export function describeTemplate(t) {
914
949
  const source = t._source ?? 'bundled';
915
950
  const age = formatCaptureAge(t._captured);
916
- return `${source} capture, CC v${t._version} (${age} old)`;
951
+ // Variant coverage is part of the template's identity: a template that
952
+ // lost its per-model variants serves every family the base prompt, and
953
+ // that degradation is otherwise invisible (dario#lock-step).
954
+ const keys = Object.keys(promptVariantsOf(t)).sort();
955
+ const variants = keys.length > 0 ? keys.join('+') : 'none';
956
+ return `${source} capture, CC v${t._version} (${age} old), variants: ${variants}`;
917
957
  }
918
958
  /**
919
959
  * Compare the loaded template's captured CC version against the version
package/dist/proxy.js CHANGED
@@ -615,9 +615,20 @@ const ORCHESTRATION_PATTERNS_DEFAULT = buildOrchestrationPatterns();
615
615
  /** Strip orchestration wrapper tags from message content. */
616
616
  function sanitizeContent(text, patterns) {
617
617
  let result = text;
618
- for (const pattern of patterns) {
619
- pattern.lastIndex = 0;
620
- result = result.replace(pattern, '');
618
+ // Fast path: every pattern in buildOrchestrationPatterns is anchored on a
619
+ // literal `<`, so a body with no `<` cannot match any of them — skip the
620
+ // ~28 full-string regex passes. sanitizeMessages runs on EVERY request,
621
+ // including byte-faithful CC traffic, and most message blocks (prose user
622
+ // turns, command/tool output) carry no tag; on a realistic mix this drops
623
+ // ~80% of the scrub's CPU while producing byte-identical output (the loop
624
+ // is skipped only when it provably cannot change the string). The trailing
625
+ // whitespace normalization still runs so no-tag blocks are treated exactly
626
+ // as before.
627
+ if (result.indexOf('<') !== -1) {
628
+ for (const pattern of patterns) {
629
+ pattern.lastIndex = 0;
630
+ result = result.replace(pattern, '');
631
+ }
621
632
  }
622
633
  return result.replace(/\n{3,}/g, '\n\n').trim();
623
634
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "5.5.5",
3
+ "version": "5.5.7",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {