@askalf/dario 4.8.66 → 4.8.68
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/dist/cli.js +12 -1
- package/dist/doctor.d.ts +33 -0
- package/dist/doctor.js +155 -7
- package/dist/live-fingerprint.d.ts +1 -1
- package/dist/live-fingerprint.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1118,6 +1118,16 @@ async function help() {
|
|
|
1118
1118
|
(Sonnet only, Opus only when Anthropic ships
|
|
1119
1119
|
them), overage. Mirrors the user-dashboard
|
|
1120
1120
|
usage page. Costs ~1 subscription request.
|
|
1121
|
+
dario doctor --obedience
|
|
1122
|
+
Probe each model family THROUGH the running
|
|
1123
|
+
proxy with a client system prompt ("reply
|
|
1124
|
+
with ONLY the word PONG") and assert the
|
|
1125
|
+
reply obeys. Catches upstream behavioral
|
|
1126
|
+
drift in client-system steering — the
|
|
1127
|
+
2026-06-12 sonnet class (dario#509) that
|
|
1128
|
+
200s, billing, and template checks all
|
|
1129
|
+
miss. Needs \`dario proxy\` running; costs
|
|
1130
|
+
at most a few tiny subscription requests.
|
|
1121
1131
|
dario doctor --json Emit the check report as structured JSON
|
|
1122
1132
|
for machine consumption (claude-bridge
|
|
1123
1133
|
/status, CI scripts, etc.) instead of the
|
|
@@ -1672,6 +1682,7 @@ async function doctor() {
|
|
|
1672
1682
|
const { runChecks, formatChecks, formatChecksJson, exitCodeFor, runAuthCheck } = await import('./doctor.js');
|
|
1673
1683
|
const probe = args.includes('--probe');
|
|
1674
1684
|
const usage = args.includes('--usage');
|
|
1685
|
+
const obedience = args.includes('--obedience');
|
|
1675
1686
|
const asJson = args.includes('--json');
|
|
1676
1687
|
const authCheck = args.includes('--auth-check');
|
|
1677
1688
|
const bunBoot = args.includes('--bun-bootstrap');
|
|
@@ -1755,7 +1766,7 @@ async function doctor() {
|
|
|
1755
1766
|
console.log('');
|
|
1756
1767
|
process.exit(result.verdict === 'match' ? 0 : 1);
|
|
1757
1768
|
}
|
|
1758
|
-
const checks = await runChecks({ probe, usage });
|
|
1769
|
+
const checks = await runChecks({ probe, usage, obedience });
|
|
1759
1770
|
if (asJson) {
|
|
1760
1771
|
// JSON mode is meant for machine consumption (claude-bridge /status,
|
|
1761
1772
|
// deepdive health checks, CI scripts) — no decorative header, no
|
package/dist/doctor.d.ts
CHANGED
|
@@ -74,6 +74,28 @@ export interface IdentityDriftInput {
|
|
|
74
74
|
}
|
|
75
75
|
export declare function checkIdentityDrift(input: IdentityDriftInput): Check[];
|
|
76
76
|
export declare function probeNpmLatestCC(): string | null;
|
|
77
|
+
/**
|
|
78
|
+
* The client system prompt the `--obedience` probe sends. Deliberately
|
|
79
|
+
* trivial: any model that weighs client system text at all can comply,
|
|
80
|
+
* so a miss isolates "the client system prompt is being ignored" from
|
|
81
|
+
* "the instruction was too hard".
|
|
82
|
+
*/
|
|
83
|
+
export declare const OBEDIENCE_SYSTEM_PROMPT = "Reply with ONLY the word PONG. No other words, no punctuation, no formatting.";
|
|
84
|
+
/**
|
|
85
|
+
* Join the text blocks of a `/v1/messages` response body. Thinking
|
|
86
|
+
* blocks are excluded — adaptive thinking may prepend them and they are
|
|
87
|
+
* not part of what the client-facing instruction governs. A refusal
|
|
88
|
+
* (empty content) or a malformed body yields ''.
|
|
89
|
+
*/
|
|
90
|
+
export declare function extractMessageText(body: unknown): string;
|
|
91
|
+
/**
|
|
92
|
+
* Verdict for one obedience reply. Lenient on case and a single trailing
|
|
93
|
+
* `.`/`!` — the drift class this detects is "the model ignored the client
|
|
94
|
+
* system prompt entirely" (it answers as the CC persona instead), and a
|
|
95
|
+
* stray "Pong!" is obedient in substance. Strict equality would file
|
|
96
|
+
* 6-hourly drift issues over punctuation sampling.
|
|
97
|
+
*/
|
|
98
|
+
export declare function isObedientReply(text: string): boolean;
|
|
77
99
|
export interface RunChecksOptions {
|
|
78
100
|
/**
|
|
79
101
|
* Opt-in: hit Anthropic's authorize endpoint with the scope set dario
|
|
@@ -95,6 +117,17 @@ export interface RunChecksOptions {
|
|
|
95
117
|
* request.
|
|
96
118
|
*/
|
|
97
119
|
usage?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Opt-in: probe each model family THROUGH the running proxy with a
|
|
122
|
+
* client system prompt ("reply with ONLY the word PONG") and assert
|
|
123
|
+
* the reply obeys. Catches upstream behavioral drift in client-system
|
|
124
|
+
* steering — the 2026-06-12 class (dario#509) where sonnet silently
|
|
125
|
+
* stopped following client system text while every other signal
|
|
126
|
+
* (200s, subscription billing, template labels, model smoke) stayed
|
|
127
|
+
* green. Enable with `dario doctor --obedience`; costs at most
|
|
128
|
+
* `families × 3` tiny subscription requests.
|
|
129
|
+
*/
|
|
130
|
+
obedience?: boolean;
|
|
98
131
|
}
|
|
99
132
|
/**
|
|
100
133
|
* Run every available health check. Never throws — each check is
|
package/dist/doctor.js
CHANGED
|
@@ -148,6 +148,52 @@ export function probeNpmLatestCC() {
|
|
|
148
148
|
_npmLatestCache = { value, at: Date.now() };
|
|
149
149
|
return value;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* One representative model per family, shared by the `--usage` probe
|
|
153
|
+
* (Anthropic only returns a family's 7d bucket header on a request TO
|
|
154
|
+
* that family) and the `--obedience` probe (behavioral drift is
|
|
155
|
+
* per-family — the 2026-06-12 regression hit sonnet while haiku obeyed
|
|
156
|
+
* the identical merged body).
|
|
157
|
+
*/
|
|
158
|
+
const PROBE_FAMILIES = [
|
|
159
|
+
{ family: 'haiku', model: 'claude-haiku-4-5' },
|
|
160
|
+
{ family: 'sonnet', model: 'claude-sonnet-4-6' },
|
|
161
|
+
{ family: 'opus', model: 'claude-opus-4-8' },
|
|
162
|
+
{ family: 'fable', model: 'claude-fable-5' },
|
|
163
|
+
];
|
|
164
|
+
/**
|
|
165
|
+
* The client system prompt the `--obedience` probe sends. Deliberately
|
|
166
|
+
* trivial: any model that weighs client system text at all can comply,
|
|
167
|
+
* so a miss isolates "the client system prompt is being ignored" from
|
|
168
|
+
* "the instruction was too hard".
|
|
169
|
+
*/
|
|
170
|
+
export const OBEDIENCE_SYSTEM_PROMPT = 'Reply with ONLY the word PONG. No other words, no punctuation, no formatting.';
|
|
171
|
+
/**
|
|
172
|
+
* Join the text blocks of a `/v1/messages` response body. Thinking
|
|
173
|
+
* blocks are excluded — adaptive thinking may prepend them and they are
|
|
174
|
+
* not part of what the client-facing instruction governs. A refusal
|
|
175
|
+
* (empty content) or a malformed body yields ''.
|
|
176
|
+
*/
|
|
177
|
+
export function extractMessageText(body) {
|
|
178
|
+
const content = body?.content;
|
|
179
|
+
if (!Array.isArray(content))
|
|
180
|
+
return '';
|
|
181
|
+
return content
|
|
182
|
+
.filter((b) => b?.type === 'text' && typeof b.text === 'string')
|
|
183
|
+
.map((b) => b.text)
|
|
184
|
+
.join('')
|
|
185
|
+
.trim();
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Verdict for one obedience reply. Lenient on case and a single trailing
|
|
189
|
+
* `.`/`!` — the drift class this detects is "the model ignored the client
|
|
190
|
+
* system prompt entirely" (it answers as the CC persona instead), and a
|
|
191
|
+
* stray "Pong!" is obedient in substance. Strict equality would file
|
|
192
|
+
* 6-hourly drift issues over punctuation sampling.
|
|
193
|
+
*/
|
|
194
|
+
export function isObedientReply(text) {
|
|
195
|
+
return /^pong[.!]?$/i.test(text.trim());
|
|
196
|
+
}
|
|
151
197
|
/**
|
|
152
198
|
* Run every available health check. Never throws — each check is
|
|
153
199
|
* individually try/caught so a broken subsystem (e.g. unreadable accounts
|
|
@@ -475,12 +521,6 @@ export async function runChecks(opts = {}) {
|
|
|
475
521
|
}
|
|
476
522
|
// Probe each family in parallel. Anthropic only returns the
|
|
477
523
|
// per-model 7d bucket header on a request TO that family.
|
|
478
|
-
const families = [
|
|
479
|
-
{ family: 'haiku', model: 'claude-haiku-4-5' },
|
|
480
|
-
{ family: 'sonnet', model: 'claude-sonnet-4-6' },
|
|
481
|
-
{ family: 'opus', model: 'claude-opus-4-8' },
|
|
482
|
-
{ family: 'fable', model: 'claude-fable-5' },
|
|
483
|
-
];
|
|
484
524
|
const probe = async (model) => {
|
|
485
525
|
const res = await fetch(probeEndpoint, {
|
|
486
526
|
method: 'POST',
|
|
@@ -499,7 +539,7 @@ export async function runChecks(opts = {}) {
|
|
|
499
539
|
return null;
|
|
500
540
|
return parseRateLimits(res.headers);
|
|
501
541
|
};
|
|
502
|
-
const results = await Promise.all(
|
|
542
|
+
const results = await Promise.all(PROBE_FAMILIES.map(f => probe(f.model).catch(() => null)));
|
|
503
543
|
// Use the first non-null snapshot for the unified view — they
|
|
504
544
|
// should all agree on the unified buckets (same account, same moment).
|
|
505
545
|
const firstOk = results.find(s => s !== null);
|
|
@@ -554,6 +594,114 @@ export async function runChecks(opts = {}) {
|
|
|
554
594
|
});
|
|
555
595
|
}
|
|
556
596
|
}
|
|
597
|
+
// ---- Client-system obedience probe (opt-in, --obedience).
|
|
598
|
+
// dario#509 / the 2026-06-12 deepdive planner outage: Anthropic's serving
|
|
599
|
+
// side changed how claude-sonnet-4-6 weighed client system text merged
|
|
600
|
+
// after the CC persona in block 3 — wire bytes identical, every existing
|
|
601
|
+
// check green — and the only symptom was a downstream consumer failing to
|
|
602
|
+
// get the JSON its system prompt demanded. This probe asserts the property
|
|
603
|
+
// those checks miss: a model, reached THROUGH the proxy's template merge,
|
|
604
|
+
// actually follows a client-supplied system instruction.
|
|
605
|
+
//
|
|
606
|
+
// Per family: up to 3 attempts (tolerates sampling), pass on the first
|
|
607
|
+
// obedient reply. Families probe in parallel, so worst-case wall time is
|
|
608
|
+
// bounded by attempts × timeout, not by family count.
|
|
609
|
+
//
|
|
610
|
+
// Requires a running proxy: a direct-to-Anthropic raw client shape would
|
|
611
|
+
// not exercise the cc-template merge seam (and Sonnet/Opus reject non-CC
|
|
612
|
+
// shapes on the subscription path anyway).
|
|
613
|
+
//
|
|
614
|
+
// Verdict semantics (consumed by scripts/check-doctor-drift.mjs):
|
|
615
|
+
// fail = the model ANSWERED but ignored the instruction. Behavioral and
|
|
616
|
+
// upstream-influenced — investigate the system-prompt
|
|
617
|
+
// presentation/merge (CLIENT_SYSTEM_PREFACE, block-3 structure),
|
|
618
|
+
// NOT necessarily a dario bug, and don't start with version
|
|
619
|
+
// bisects (the 2026-06-12 incident burned hours on those; they
|
|
620
|
+
// were all negative because nothing on dario's side changed).
|
|
621
|
+
// warn = the probe never got an answer (network/429/5xx) — infra
|
|
622
|
+
// flake, not drift.
|
|
623
|
+
if (opts.obedience) {
|
|
624
|
+
const dario_base = process.env.DARIO_TEST_URL || 'http://127.0.0.1:3456';
|
|
625
|
+
// Same auth fallback as the --usage probe: the proxy validates
|
|
626
|
+
// Authorization against DARIO_API_KEY when set; literal 'dario' is the
|
|
627
|
+
// documented loopback-only default for no-auth local proxies.
|
|
628
|
+
const dario_auth = process.env.DARIO_API_KEY || 'dario';
|
|
629
|
+
let proxyUp = false;
|
|
630
|
+
try {
|
|
631
|
+
const healthRes = await fetch(`${dario_base}/health`, { signal: AbortSignal.timeout(800) });
|
|
632
|
+
proxyUp = healthRes.ok;
|
|
633
|
+
}
|
|
634
|
+
catch { /* proxy not running */ }
|
|
635
|
+
if (!proxyUp) {
|
|
636
|
+
checks.push({
|
|
637
|
+
status: 'info',
|
|
638
|
+
label: 'Obedience',
|
|
639
|
+
detail: 'dario proxy not running — skipped. The probe must route through the proxy to exercise the client-system merge; start `dario proxy` and re-run.',
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
const ATTEMPTS = 3;
|
|
644
|
+
const probeFamily = async ({ family, model }) => {
|
|
645
|
+
let lastReply = '';
|
|
646
|
+
let lastErr = null;
|
|
647
|
+
for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
|
|
648
|
+
try {
|
|
649
|
+
const res = await fetch(`${dario_base}/v1/messages`, {
|
|
650
|
+
method: 'POST',
|
|
651
|
+
headers: {
|
|
652
|
+
'content-type': 'application/json',
|
|
653
|
+
'anthropic-version': '2023-06-01',
|
|
654
|
+
'authorization': `Bearer ${dario_auth}`,
|
|
655
|
+
},
|
|
656
|
+
body: JSON.stringify({
|
|
657
|
+
model,
|
|
658
|
+
max_tokens: 64,
|
|
659
|
+
system: OBEDIENCE_SYSTEM_PROMPT,
|
|
660
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
661
|
+
}),
|
|
662
|
+
signal: AbortSignal.timeout(30_000),
|
|
663
|
+
});
|
|
664
|
+
if (!res.ok) {
|
|
665
|
+
await res.text().catch(() => '');
|
|
666
|
+
lastErr = `HTTP ${res.status}`;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const body = await res.json().catch(() => null);
|
|
670
|
+
const text = extractMessageText(body);
|
|
671
|
+
if (isObedientReply(text)) {
|
|
672
|
+
return {
|
|
673
|
+
status: 'ok',
|
|
674
|
+
label: `Obedience (${family})`,
|
|
675
|
+
detail: `"${text}" (attempt ${attempt}/${ATTEMPTS})`,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
// A completed-but-disobedient reply (including a refusal's
|
|
679
|
+
// empty content) is the drift signal — remember it so it
|
|
680
|
+
// dominates over any later transport error.
|
|
681
|
+
lastReply = text;
|
|
682
|
+
lastErr = null;
|
|
683
|
+
}
|
|
684
|
+
catch (err) {
|
|
685
|
+
lastErr = err.message;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (lastErr !== null && lastReply === '') {
|
|
689
|
+
return {
|
|
690
|
+
status: 'warn',
|
|
691
|
+
label: `Obedience (${family})`,
|
|
692
|
+
detail: `probe could not complete (${lastErr}) — infra flake, not behavioral drift`,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
status: 'fail',
|
|
697
|
+
label: `Obedience (${family})`,
|
|
698
|
+
detail: `model answered but ignored the client system prompt (last reply: "${lastReply.slice(0, 80)}") — behavioral, upstream-influenced; investigate presentation/merge (CLIENT_SYSTEM_PREFACE seam), not necessarily a dario bug`,
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
const rows = await Promise.all(PROBE_FAMILIES.map((f) => probeFamily(f)));
|
|
702
|
+
checks.push(...rows);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
557
705
|
// ---- Account pool
|
|
558
706
|
try {
|
|
559
707
|
const { listAccountAliases, loadAllAccounts } = await import('./accounts.js');
|
|
@@ -282,7 +282,7 @@ export declare function _resetInstalledVersionProbeForTest(): void;
|
|
|
282
282
|
*/
|
|
283
283
|
export declare const SUPPORTED_CC_RANGE: {
|
|
284
284
|
readonly min: "1.0.0";
|
|
285
|
-
readonly maxTested: "2.1.
|
|
285
|
+
readonly maxTested: "2.1.176";
|
|
286
286
|
};
|
|
287
287
|
/**
|
|
288
288
|
* Compare two dotted-numeric version strings. Returns negative if `a<b`,
|
package/dist/live-fingerprint.js
CHANGED
|
@@ -786,7 +786,7 @@ export function _resetInstalledVersionProbeForTest() {
|
|
|
786
786
|
*/
|
|
787
787
|
export const SUPPORTED_CC_RANGE = {
|
|
788
788
|
min: '1.0.0',
|
|
789
|
-
maxTested: '2.1.
|
|
789
|
+
maxTested: '2.1.176',
|
|
790
790
|
};
|
|
791
791
|
/**
|
|
792
792
|
* Compare two dotted-numeric version strings. Returns negative if `a<b`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.68",
|
|
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": {
|