auxilo-mcp 0.9.26 → 0.9.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/README.md +4 -0
- package/bin/auxilo-cli.js +2 -2
- package/mcp-server.js +6 -6
- package/package.json +1 -1
- package/scripts/extract-local.js +28 -4
- package/scripts/providers/byo-key.js +20 -5
- package/scripts/providers/claude-code.js +105 -142
- package/scripts/providers/codex-cli.js +25 -9
- package/scripts/providers/index.js +134 -155
- package/scripts/providers/provider.interface.js +24 -17
- package/scripts/providers/route-binding.js +232 -0
- package/scripts/runner.js +20 -2
package/README.md
CHANGED
|
@@ -175,6 +175,10 @@ node mcp-server.js # the MCP server (stdio)
|
|
|
175
175
|
|
|
176
176
|
The live API runs on [Fly.io](https://fly.io) from the repo `Dockerfile`.
|
|
177
177
|
|
|
178
|
+
### One machine
|
|
179
|
+
|
|
180
|
+
The money ledgers are guarded by locks held in memory in one running process. The server is meant to run as one machine with one data volume. Do not run a second machine against the same data, and do not start an overlapping deploy where the old and new process both run against the same files at once — the locks on one machine cannot see or coordinate with a second one, so two processes writing the same ledger files at the same time can corrupt them or double-spend a balance. This must not change until the ledgers move to a shared store that more than one process can safely coordinate through.
|
|
181
|
+
|
|
178
182
|
## Links
|
|
179
183
|
|
|
180
184
|
- Site: [auxilo.io](https://auxilo.io)
|
package/bin/auxilo-cli.js
CHANGED
|
@@ -180,8 +180,8 @@ const CONSENT_TEXT = `
|
|
|
180
180
|
waits for operator review. A draft that any screen flags (sensitive,
|
|
181
181
|
duplicate, uncertain quality) waits in your private queue for
|
|
182
182
|
\`auxilo review\`. Auto-publish for learnings that pass every screen is
|
|
183
|
-
off unless you turn it on in your dashboard. Your
|
|
184
|
-
unlock by another agent
|
|
183
|
+
off unless you turn it on in your dashboard. Your earnings from a paid
|
|
184
|
+
unlock by another agent go to your Auxilo account, 70% of what they
|
|
185
185
|
paid on a direct unlock and 60% via discovery. A repeat unlock by the
|
|
186
186
|
same buyer within 30 days earns nothing. Earnings depend on whether
|
|
187
187
|
other agents unlock your learnings and are not guaranteed. Earnings
|
package/mcp-server.js
CHANGED
|
@@ -77,7 +77,7 @@ function unlockPaymentRequired(status, data, http_endpoint) {
|
|
|
77
77
|
status: 'payment_required',
|
|
78
78
|
content_advisory: UNTRUSTED_PREVIEW_ADVISORY,
|
|
79
79
|
cost: `${price} USDC on Base (set by contributor)`,
|
|
80
|
-
how_to_pay: 'Pass an x402 payment via the x_payment argument, or configure an API key with
|
|
80
|
+
how_to_pay: 'Pass an x402 payment via the x_payment argument, or configure an API key with a funded balance (npx auxilo setup).',
|
|
81
81
|
http_endpoint,
|
|
82
82
|
payment_details: fencePaymentChallenge(data),
|
|
83
83
|
};
|
|
@@ -198,7 +198,7 @@ async function postBulkChunks(headers, decisions) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
const server = new Server(
|
|
201
|
-
{ name: 'auxilo', version: '0.9.
|
|
201
|
+
{ name: 'auxilo', version: '0.9.27' },
|
|
202
202
|
{
|
|
203
203
|
capabilities: { tools: {} },
|
|
204
204
|
instructions: `You are connected to Auxilo, a knowledge marketplace where AI agents buy and sell operational learnings.
|
|
@@ -217,7 +217,7 @@ RATE AFTER YOU USE: After unlocking and applying a learning from the Auxilo mark
|
|
|
217
217
|
|
|
218
218
|
DEDUP BEFORE SUBMITTING: Search auxilo_knowledge for your topic before contributing to avoid duplicates.
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
When another agent unlocks one of your contributions, the builder who connected you earns 70% of what that agent paid on a direct unlock and 60% when Auxilo search surfaced it.`
|
|
221
221
|
}
|
|
222
222
|
);
|
|
223
223
|
|
|
@@ -298,7 +298,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
298
298
|
},
|
|
299
299
|
required: ['specificity', 'actionability', 'novelty', 'completeness', 'total'],
|
|
300
300
|
},
|
|
301
|
-
contributor_wallet: { type: 'string', description: 'Your Base wallet (0x...) for
|
|
301
|
+
contributor_wallet: { type: 'string', description: 'Your Base wallet (0x...) for earnings' },
|
|
302
302
|
unlock_price: { type: 'number', description: 'Price in USD to unlock this learning (min $0.05, default auto-calculated). Set higher for deep, high-value knowledge.' },
|
|
303
303
|
contributor_agent: { type: 'string', description: 'Optional: identify yourself' },
|
|
304
304
|
related_skills: { type: 'array', items: { type: 'string' }, description: 'Optional: related Auxilo skill IDs' },
|
|
@@ -362,7 +362,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
362
362
|
},
|
|
363
363
|
{
|
|
364
364
|
name: 'auxilo_withdraw',
|
|
365
|
-
description: 'Check how your earnings are paid out, and how to receive any legacy accrued balance. HOW PAYOUT WORKS NOW: earnings from sales settled on-chain are paid directly to your linked, verified wallet at the moment of sale — there is no balance to withdraw for those, because Auxilo never holds your
|
|
365
|
+
description: 'Check how your earnings are paid out, and how to receive any legacy accrued balance. HOW PAYOUT WORKS NOW: earnings from sales settled on-chain are paid directly to your linked, verified wallet at the moment of sale — there is no balance to withdraw for those, because Auxilo never holds your earnings. LEGACY BALANCE: earnings accrued before direct settlement sit in your account\'s pending balance; withdraw them from your dashboard at https://auxilo.io/dashboard (bank payout via Stripe; $0.50 minimum, $0.25 flat fee), where you sign in with the email on your account. This tool reports your current balances and payout status; it does not move funds. To be paid at sale time going forward: auxilo_accept_terms, then auxilo_verify_wallet, then auxilo_link_wallet.',
|
|
366
366
|
inputSchema: {
|
|
367
367
|
type: 'object',
|
|
368
368
|
properties: {
|
|
@@ -408,7 +408,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
408
408
|
},
|
|
409
409
|
{
|
|
410
410
|
name: 'auxilo_account_earnings',
|
|
411
|
-
description: 'View aggregate and per-learning earnings for your authenticated Auxilo account. Authenticates with your configured API key automatically, or pass a session_token (JWT). Returns total gross, contributor
|
|
411
|
+
description: 'View aggregate and per-learning earnings for your authenticated Auxilo account. Authenticates with your configured API key automatically, or pass a session_token (JWT). Returns total gross, contributor earnings, pending balance, total withdrawn, whether withdrawal is available (can_withdraw), and held_pending_assent — undisbursable receipts recorded before you accepted the current Terms, released to your withdrawable balance when you accept via auxilo_accept_terms. Earnings from on-chain-settled sales are paid to your wallet at sale time and appear in settlement history, not in pending balance. Free.',
|
|
412
412
|
inputSchema: {
|
|
413
413
|
type: 'object',
|
|
414
414
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auxilo-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.27",
|
|
4
4
|
"mcpName": "io.github.silent-architects/auxilo",
|
|
5
5
|
"description": "MCP server for Auxilo. Your agent stops solving the same problem twice: auto-extracted learnings, free self-unlocks, and earnings when other agents unlock yours.",
|
|
6
6
|
"main": "mcp-server.js",
|
package/scripts/extract-local.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const os = require('os');
|
|
18
|
+
const crypto = require('crypto');
|
|
18
19
|
const {
|
|
19
20
|
loadIndexForExtraction,
|
|
20
21
|
readExtractionIndex,
|
|
@@ -373,9 +374,10 @@ function resolveExtractionModelIdentity(runModelResult) {
|
|
|
373
374
|
&& typeof runModelResult.identity.provider === 'string'
|
|
374
375
|
&& runModelResult.identity.provider
|
|
375
376
|
) {
|
|
376
|
-
|
|
377
|
+
const identity = runModelResult.identity;
|
|
378
|
+
return { ...identity, model: identity.observed_model || null };
|
|
377
379
|
}
|
|
378
|
-
return { provider: 'unknown', model: null, version: null, vendor: null };
|
|
380
|
+
return { provider: 'unknown', model: null, requested_model: null, observed_model: null, identity_unresolved: 'missing', version: null, vendor: null };
|
|
379
381
|
}
|
|
380
382
|
|
|
381
383
|
/**
|
|
@@ -402,6 +404,8 @@ async function defaultInvokeModel(transcript, invokeOpts, opts) {
|
|
|
402
404
|
reasonCode: result.reasonCode,
|
|
403
405
|
authStatus: result.authStatus,
|
|
404
406
|
extractionModel: resolveExtractionModelIdentity(result),
|
|
407
|
+
...(result.hold && { hold: result.hold }),
|
|
408
|
+
...(result.deferred && { deferred: result.deferred }),
|
|
405
409
|
...(result.authDiscrepancy !== undefined && { authDiscrepancy: result.authDiscrepancy }),
|
|
406
410
|
// EXTRACTION-RUN-LOG (0.9.15): additive passthrough for the one-line-per-run
|
|
407
411
|
// provider summary logged at the end of extractLocally() below. Only
|
|
@@ -432,6 +436,9 @@ function defaultInvokeJudge(opts) {
|
|
|
432
436
|
out: result.text,
|
|
433
437
|
usage: result.usage,
|
|
434
438
|
reason: result.reason,
|
|
439
|
+
identity: result.identity,
|
|
440
|
+
...(result.hold && { hold: result.hold }),
|
|
441
|
+
...(result.deferred && { deferred: result.deferred }),
|
|
435
442
|
// EXTRACTION-RUN-LOG (0.9.15): threaded through so runAnchoredJudge below
|
|
436
443
|
// can report the judge call's status/argv/CLI version on the run summary
|
|
437
444
|
// log line. Additive — every existing invokeJudge stub in this repo's
|
|
@@ -526,11 +533,16 @@ async function runAnchoredJudge(candidates, indexState, opts = {}) {
|
|
|
526
533
|
judgeReasonCode: result && result.reasonCode,
|
|
527
534
|
judgeArgv: result && result.argv,
|
|
528
535
|
judgeCliVersion: result && result.cliVersion,
|
|
536
|
+
...(result && result.hold && { hold: result.hold }),
|
|
537
|
+
...(result && result.deferred && { deferred: result.deferred }),
|
|
529
538
|
};
|
|
530
539
|
}
|
|
531
540
|
const parsed = parseJudgeDecisions(result.out, input, rankings);
|
|
532
541
|
const usage = judgeUsage(result.usage, prompt, result.out);
|
|
533
542
|
const judgeMeta = {
|
|
543
|
+
identity: result.identity,
|
|
544
|
+
...(result.hold && { hold: result.hold }),
|
|
545
|
+
...(result.deferred && { deferred: result.deferred }),
|
|
534
546
|
judgeAttempted: true,
|
|
535
547
|
judgeReasonCode: result.reasonCode,
|
|
536
548
|
judgeArgv: result.argv,
|
|
@@ -736,6 +748,8 @@ function logProviderRunSummary(opts, runId, modelResult, judged) {
|
|
|
736
748
|
}
|
|
737
749
|
|
|
738
750
|
async function extractLocally(transcript, sourceType, opts = {}) {
|
|
751
|
+
opts = { ...opts, source: sourceType || '', sessionId: opts.sessionId || opts.runId || crypto.randomUUID(),
|
|
752
|
+
jobSha: opts.jobSha || crypto.createHash('sha256').update(transcript).digest('hex'), routeContext: opts.routeContext || {} };
|
|
739
753
|
if (sourceType && !EXTRACTABLE_SOURCES.has(sourceType)) {
|
|
740
754
|
return { learnings: [], skipped: `local extraction not implemented for "${sourceType}" — agent contributes via auxilo_contribute` };
|
|
741
755
|
}
|
|
@@ -780,6 +794,8 @@ async function extractLocally(transcript, sourceType, opts = {}) {
|
|
|
780
794
|
return {
|
|
781
795
|
learnings: [],
|
|
782
796
|
skipped: reason,
|
|
797
|
+
...(modelResult.hold && { hold: modelResult.hold }),
|
|
798
|
+
...(modelResult.deferred && { deferred: modelResult.deferred }),
|
|
783
799
|
...(modelResult.reasonCode !== undefined && { reasonCode: modelResult.reasonCode }),
|
|
784
800
|
...(modelResult.authStatus !== undefined && { authStatus: modelResult.authStatus }),
|
|
785
801
|
...(modelResult.authDiscrepancy !== undefined && {
|
|
@@ -792,7 +808,9 @@ async function extractLocally(transcript, sourceType, opts = {}) {
|
|
|
792
808
|
// own opts.invokeModel that doesn't report one (every existing test does
|
|
793
809
|
// this) — extraction_model then simply never appears, byte-identical to
|
|
794
810
|
// pre-PART-C behavior.
|
|
795
|
-
const extractionModel = modelResult.extractionModel
|
|
811
|
+
const extractionModel = modelResult.extractionModel
|
|
812
|
+
? { ...modelResult.extractionModel, model: modelResult.extractionModel.observed_model || null }
|
|
813
|
+
: null;
|
|
796
814
|
const parsed = parseExtractionOutput(out, opts);
|
|
797
815
|
const promptDropResult = applyPromptMemoryDrops(
|
|
798
816
|
parsed.prompt_drops,
|
|
@@ -830,7 +848,10 @@ async function extractLocally(transcript, sourceType, opts = {}) {
|
|
|
830
848
|
lexicalKept.push(drop.candidate);
|
|
831
849
|
}
|
|
832
850
|
}
|
|
833
|
-
const judged =
|
|
851
|
+
const judged = modelResult.hold || modelResult.deferred
|
|
852
|
+
? { kept: lexicalKept, dropped: [], called: false, prompt_tokens: 0, completion_tokens: 0,
|
|
853
|
+
...(modelResult.hold && { hold: modelResult.hold }), ...(modelResult.deferred && { deferred: modelResult.deferred }) }
|
|
854
|
+
: await runAnchoredJudge(lexicalKept, filterState, opts);
|
|
834
855
|
const allDropped = [
|
|
835
856
|
...promptDropResult.dropped,
|
|
836
857
|
...lexicalDropped,
|
|
@@ -846,6 +867,9 @@ async function extractLocally(transcript, sourceType, opts = {}) {
|
|
|
846
867
|
logProviderRunSummary(opts, opts.runId, modelResult, judged);
|
|
847
868
|
return {
|
|
848
869
|
learnings: judged.kept,
|
|
870
|
+
...(judged.hold && { hold: judged.hold }),
|
|
871
|
+
...(judged.deferred && { deferred: judged.deferred }),
|
|
872
|
+
...providers.finishJob(opts.routeContext),
|
|
849
873
|
dedup_dropped: allDropped.length,
|
|
850
874
|
drop_audit: allDropped.map((drop) => drop.audit),
|
|
851
875
|
prompt_memory_tokens: promptMemory.estimated_tokens,
|
|
@@ -537,6 +537,7 @@ async function runModel(opts = {}) {
|
|
|
537
537
|
usage: null,
|
|
538
538
|
reason: 'cannot resolve ~/.auxilo/providers.json — the home directory did not resolve to an absolute path',
|
|
539
539
|
reasonCode: 'provider-home-unresolved',
|
|
540
|
+
refusal: 'pre-invocation',
|
|
540
541
|
authStatus: 'unknown',
|
|
541
542
|
};
|
|
542
543
|
}
|
|
@@ -552,6 +553,7 @@ async function runModel(opts = {}) {
|
|
|
552
553
|
usage: null,
|
|
553
554
|
reason: '~/.auxilo/providers.json is not owner-read-only; refusing to use the stored key until its permissions are fixed (chmod 600 ~/.auxilo/providers.json) or the file is removed (`auxilo provider clear`)',
|
|
554
555
|
reasonCode: 'providers-file-mode-unsafe',
|
|
556
|
+
refusal: 'pre-invocation',
|
|
555
557
|
authStatus: 'unknown',
|
|
556
558
|
};
|
|
557
559
|
}
|
|
@@ -563,6 +565,7 @@ async function runModel(opts = {}) {
|
|
|
563
565
|
usage: null,
|
|
564
566
|
reason: 'no BYO provider key configured — run `auxilo provider set`',
|
|
565
567
|
reasonCode: 'provider-not-configured',
|
|
568
|
+
refusal: 'pre-invocation',
|
|
566
569
|
authStatus: 'unknown',
|
|
567
570
|
};
|
|
568
571
|
}
|
|
@@ -575,8 +578,9 @@ async function runModel(opts = {}) {
|
|
|
575
578
|
ok: false,
|
|
576
579
|
text: '',
|
|
577
580
|
usage: null,
|
|
578
|
-
reason:
|
|
581
|
+
reason: 'configured base URL is not https; refusing the request',
|
|
579
582
|
reasonCode: 'provider-base-url-insecure',
|
|
583
|
+
refusal: 'pre-invocation',
|
|
580
584
|
authStatus: 'unknown',
|
|
581
585
|
};
|
|
582
586
|
}
|
|
@@ -587,7 +591,7 @@ async function runModel(opts = {}) {
|
|
|
587
591
|
const fetchImpl = typeof opts.fetchImpl === 'function' ? opts.fetchImpl : fetch;
|
|
588
592
|
const timeoutMs = opts.timeoutMs || 120000;
|
|
589
593
|
|
|
590
|
-
const identity = { provider: 'byo-key', model: config.model, version: null, vendor };
|
|
594
|
+
const identity = { provider: 'byo-key', model: null, requested_model: config.model, observed_model: null, identity_unresolved: 'missing', version: null, vendor };
|
|
591
595
|
|
|
592
596
|
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
593
597
|
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
@@ -595,6 +599,8 @@ async function runModel(opts = {}) {
|
|
|
595
599
|
|
|
596
600
|
try {
|
|
597
601
|
let res;
|
|
602
|
+
const gate = require('./route-binding.js').invocationGate(opts);
|
|
603
|
+
if (gate) return gate;
|
|
598
604
|
try {
|
|
599
605
|
res = await fetchImpl(request.url, {
|
|
600
606
|
method: 'POST',
|
|
@@ -611,7 +617,7 @@ async function runModel(opts = {}) {
|
|
|
611
617
|
ok: false,
|
|
612
618
|
text: '',
|
|
613
619
|
usage: null,
|
|
614
|
-
reason:
|
|
620
|
+
reason: 'BYO provider request failed',
|
|
615
621
|
reasonCode: 'provider-error',
|
|
616
622
|
authStatus: 'unknown',
|
|
617
623
|
identity,
|
|
@@ -634,7 +640,7 @@ async function runModel(opts = {}) {
|
|
|
634
640
|
ok: false,
|
|
635
641
|
text: '',
|
|
636
642
|
usage: null,
|
|
637
|
-
reason: `BYO provider returned HTTP ${res.status}
|
|
643
|
+
reason: Number.isInteger(res.status) ? `BYO provider returned HTTP ${res.status}` : 'BYO provider request failed',
|
|
638
644
|
reasonCode: 'provider-error',
|
|
639
645
|
authStatus: 'unknown',
|
|
640
646
|
identity,
|
|
@@ -660,13 +666,19 @@ async function runModel(opts = {}) {
|
|
|
660
666
|
ok: false,
|
|
661
667
|
text: '',
|
|
662
668
|
usage: null,
|
|
663
|
-
reason:
|
|
669
|
+
reason: 'BYO provider returned a non-JSON body',
|
|
664
670
|
reasonCode: 'provider-error',
|
|
665
671
|
authStatus: 'unknown',
|
|
666
672
|
identity,
|
|
667
673
|
};
|
|
668
674
|
}
|
|
669
675
|
|
|
676
|
+
const observed = data && (vendor === 'gemini' ? data.modelVersion : data.model);
|
|
677
|
+
if (typeof observed === 'string' && observed.trim()) {
|
|
678
|
+
identity.observed_model = observed;
|
|
679
|
+
identity.model = observed;
|
|
680
|
+
delete identity.identity_unresolved;
|
|
681
|
+
}
|
|
670
682
|
return {
|
|
671
683
|
ok: true,
|
|
672
684
|
text: extractText(vendor, data),
|
|
@@ -675,6 +687,8 @@ async function runModel(opts = {}) {
|
|
|
675
687
|
authStatus: 'unknown',
|
|
676
688
|
identity,
|
|
677
689
|
};
|
|
690
|
+
} catch {
|
|
691
|
+
return { ok: false, text: '', usage: null, reason: 'BYO provider request failed', reasonCode: 'provider-error', authStatus: 'unknown' };
|
|
678
692
|
} finally {
|
|
679
693
|
clearTimer();
|
|
680
694
|
}
|
|
@@ -689,6 +703,7 @@ module.exports = {
|
|
|
689
703
|
writeByoConfig,
|
|
690
704
|
clearProvidersFile,
|
|
691
705
|
resolveVendor,
|
|
706
|
+
baseUrlFor,
|
|
692
707
|
DEFAULT_PROVIDERS_STATE_PATH,
|
|
693
708
|
// Exported for direct unit coverage (test/byo-key-provider.test.js,
|
|
694
709
|
// test/extract-w1-fix2.test.js) AND for scripts/providers/index.js, which
|
|
@@ -21,6 +21,7 @@ const { spawnSync } = require('child_process');
|
|
|
21
21
|
const fs = require('fs');
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const os = require('os');
|
|
24
|
+
const { invocationGate } = require('./route-binding.js');
|
|
24
25
|
|
|
25
26
|
/** Leading major.minor.patch only; unreadable versions retain fallback behavior. */
|
|
26
27
|
function versionParts(version) {
|
|
@@ -219,7 +220,7 @@ const SCRUBBED_CLIENT_ENV_VARS = Object.freeze([
|
|
|
219
220
|
// SETTING_SOURCES_ARGS to both — the child loads none of user/project/local
|
|
220
221
|
// settings, so the operator's own SessionStart hooks never fire.
|
|
221
222
|
// Strict mode with no --mcp-config also excludes account-connected MCP servers.
|
|
222
|
-
const EXTRACT_MODE_ARGV = Object.freeze(['-p', '--no-session-persistence', '--tools', '', ...SETTING_SOURCES_ARGS, '--strict-mcp-config']);
|
|
223
|
+
const EXTRACT_MODE_ARGV = Object.freeze(['-p', '--output-format', 'json', '--no-session-persistence', '--tools', '', ...SETTING_SOURCES_ARGS, '--strict-mcp-config']);
|
|
223
224
|
const JUDGE_MODE_ARGV = Object.freeze(['-p', '--output-format', 'json', '--no-session-persistence', '--tools', '', ...SETTING_SOURCES_ARGS, '--strict-mcp-config']);
|
|
224
225
|
|
|
225
226
|
/**
|
|
@@ -402,9 +403,8 @@ function checkAuthStatus(opts = {}) {
|
|
|
402
403
|
const spawnSyncImpl = typeof opts.spawnSyncImpl === 'function' ? opts.spawnSyncImpl : spawnSync;
|
|
403
404
|
const bin = typeof opts.claudeBin === 'string' ? opts.claudeBin : resolveClaudeBin(opts);
|
|
404
405
|
const version = versionParts(getClaudeCliVersion(bin, opts));
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
if (version && compareVersions(version, [2, 1, 41]) < 0) return 'unknown';
|
|
406
|
+
// Unknown and old versions can interpret this probe as a model prompt.
|
|
407
|
+
if (!version || compareVersions(version, [2, 1, 41]) < 0) return 'unknown';
|
|
408
408
|
let res;
|
|
409
409
|
try {
|
|
410
410
|
res = spawnSyncImpl(bin, ['auth', 'status'], {
|
|
@@ -455,84 +455,108 @@ function detect(opts = {}) {
|
|
|
455
455
|
* needs to await beyond the spawn itself); `runModel` wraps it in a resolved
|
|
456
456
|
* Promise, per the provider.interface.js contract.
|
|
457
457
|
*/
|
|
458
|
-
function runExtractMode(opts) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
458
|
+
function runExtractMode(opts) { return runCliMode(opts, 'extract'); }
|
|
459
|
+
|
|
460
|
+
function decodeWrapper(stdout) {
|
|
461
|
+
let wrapper;
|
|
462
|
+
try { wrapper = JSON.parse(stdout); }
|
|
463
|
+
catch {
|
|
464
|
+
const values = [];
|
|
465
|
+
const lines = stdout.split('\n');
|
|
466
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
467
|
+
let value = lines[i].trim();
|
|
468
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
469
|
+
// Consume the whole container, including pretty-printed extra values.
|
|
470
|
+
// A line-only JSON.parse scan could silently overlook that second value.
|
|
471
|
+
let depth = 0; let quoted = false; let escaped = false; let closed = false;
|
|
472
|
+
const parts = [];
|
|
473
|
+
for (; i < lines.length; i += 1) {
|
|
474
|
+
const line = lines[i]; parts.push(line);
|
|
475
|
+
for (let j = 0; j < line.length; j += 1) {
|
|
476
|
+
const char = line[j];
|
|
477
|
+
if (quoted) {
|
|
478
|
+
if (escaped) escaped = false;
|
|
479
|
+
else if (char === '\\') escaped = true;
|
|
480
|
+
else if (char === '"') quoted = false;
|
|
481
|
+
} else if (char === '"') quoted = true;
|
|
482
|
+
else if (char === '{' || char === '[') depth += 1;
|
|
483
|
+
else if (char === '}' || char === ']') {
|
|
484
|
+
depth -= 1;
|
|
485
|
+
if (depth === 0) {
|
|
486
|
+
if (line.slice(j + 1).trim()) return { malformed: true };
|
|
487
|
+
closed = true; break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (closed) break;
|
|
492
|
+
}
|
|
493
|
+
if (!closed) return { malformed: true };
|
|
494
|
+
value = parts.join('\n');
|
|
495
|
+
try { values.push(JSON.parse(value)); } catch { return { malformed: true }; }
|
|
496
|
+
} else {
|
|
497
|
+
try { values.push(JSON.parse(value)); } catch { /* non-JSON diagnostic */ }
|
|
498
|
+
}
|
|
499
|
+
if (values.length > 1) return { malformed: true };
|
|
500
|
+
}
|
|
501
|
+
if (values.length !== 1 || !values[0] || values[0].type !== 'result') return { malformed: true };
|
|
502
|
+
[wrapper] = values;
|
|
472
503
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
504
|
+
if (!wrapper || wrapper.type !== 'result' || typeof wrapper.result !== 'string' || wrapper.is_error === true) {
|
|
505
|
+
return { unsuccessful: true, wrapper };
|
|
506
|
+
}
|
|
507
|
+
return { wrapper };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function wrapperIdentity(wrapper, cliVersion) {
|
|
511
|
+
const models = [...new Set(Object.values(wrapper.modelUsage || {}).map(entry => entry && entry.canonicalModel)
|
|
512
|
+
.filter(value => typeof value === 'string' && value.trim()))];
|
|
513
|
+
const observed = models.length === 1 ? models[0] : null;
|
|
514
|
+
return { provider: 'claude-code', model: observed, requested_model: null, observed_model: observed,
|
|
515
|
+
version: cliVersion || null, vendor: 'anthropic',
|
|
516
|
+
...(observed === null && { identity_unresolved: models.length ? 'ambiguous' : 'missing' }) };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function runCliMode(opts, mode) {
|
|
520
|
+
const spawn = typeof opts.spawnSyncImpl === 'function' ? opts.spawnSyncImpl : spawnSync;
|
|
521
|
+
const bin = typeof opts.claudeBin === 'string' ? opts.claudeBin : resolveClaudeBin(opts);
|
|
522
|
+
if (cachedSettingSourcesUnsupported) return { ...settingSourcesIsolationUnsupportedResult('unknown'), refusal: 'pre-invocation' };
|
|
523
|
+
const authStatus = mode === 'extract' ? checkAuthStatus({ ...opts, claudeBin: bin }) : 'unknown';
|
|
524
|
+
const authReason = 'local model not authenticated in this context (run `claude auth login` once); skipping deterministic extraction';
|
|
525
|
+
if (authStatus === 'logged-out') return { ok: false, text: '', usage: null, reason: authReason,
|
|
526
|
+
reasonCode: 'cli-unauthenticated', authStatus, refusal: 'pre-invocation' };
|
|
527
|
+
const argv = mode === 'judge' ? JUDGE_MODE_ARGV : EXTRACT_MODE_ARGV;
|
|
481
528
|
const cliVersion = getClaudeCliVersion(bin, opts);
|
|
529
|
+
const meta = { authStatus, argv, cliVersion };
|
|
530
|
+
const fail = (reasonCode, reason) => ({ ok: false, text: '', usage: null, reasonCode, reason, ...meta });
|
|
531
|
+
const gate = invocationGate(opts);
|
|
532
|
+
if (gate) return { ...gate, ...meta };
|
|
482
533
|
let res;
|
|
483
534
|
try {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
input: stdin,
|
|
488
|
-
encoding: 'utf-8',
|
|
489
|
-
env: claudeChildEnv(),
|
|
490
|
-
timeout: opts.timeoutMs || 120000,
|
|
491
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
535
|
+
res = spawn(bin, argv, {
|
|
536
|
+
input: (typeof opts.prompt === 'string' ? opts.prompt : '') + (mode === 'extract' ? String(opts.input || '').slice(0, 200000) : ''),
|
|
537
|
+
encoding: 'utf-8', env: claudeChildEnv(), timeout: opts.timeoutMs || 120000, maxBuffer: 20 * 1024 * 1024,
|
|
492
538
|
});
|
|
493
|
-
} catch (
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
if (!res) {
|
|
497
|
-
return { ok: false, text: '', usage: null, reason: `spawn failed (${bin}): no process result`, reasonCode: 'unknown', authStatus, argv, cliVersion };
|
|
498
|
-
}
|
|
539
|
+
} catch { return fail('unknown', 'local model spawn failed'); }
|
|
540
|
+
if (!res) return fail('unknown', 'local model returned no process result');
|
|
499
541
|
if (looksLikeUnsupportedSettingSourcesFlag(res)) {
|
|
500
542
|
cachedSettingSourcesUnsupported = true;
|
|
501
543
|
return { ...settingSourcesIsolationUnsupportedResult(authStatus), argv, cliVersion };
|
|
502
544
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (
|
|
511
|
-
return {
|
|
512
|
-
ok: false,
|
|
513
|
-
text: out,
|
|
514
|
-
usage: null,
|
|
515
|
-
reason: 'local model not authenticated in this context (run `claude auth login` once); skipping deterministic extraction',
|
|
516
|
-
reasonCode: 'cli-unauthenticated',
|
|
517
|
-
authStatus,
|
|
518
|
-
argv,
|
|
519
|
-
cliVersion,
|
|
520
|
-
...(authStatus === 'logged-in' && { authDiscrepancy: true }),
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
if (res.status !== 0) {
|
|
524
|
-
return {
|
|
525
|
-
ok: false,
|
|
526
|
-
text: out,
|
|
527
|
-
usage: null,
|
|
528
|
-
reason: `local model exited ${res.status}: ${(out || String(res.stderr || '')).slice(0, 160)}`,
|
|
529
|
-
reasonCode: 'model-error',
|
|
530
|
-
authStatus,
|
|
531
|
-
argv,
|
|
532
|
-
cliVersion,
|
|
533
|
-
};
|
|
545
|
+
if (res.error || (res.signal && res.status === null)) return fail('unknown', 'local model invocation failed');
|
|
546
|
+
const mcp = enterpriseMcpRefusal(res, authStatus);
|
|
547
|
+
if (mcp) return { ...mcp, argv, cliVersion };
|
|
548
|
+
const stdout = String(res.stdout || '');
|
|
549
|
+
const decoded = decodeWrapper(stdout);
|
|
550
|
+
// A wrapper's result is content, never an authentication signal.
|
|
551
|
+
const authPattern = /Please run \/login|authentication_error/i;
|
|
552
|
+
if ((!decoded.wrapper && authPattern.test(stdout)) || authPattern.test(String(res.stderr || ''))) {
|
|
553
|
+
return { ...fail('cli-unauthenticated', authReason), ...(authStatus === 'logged-in' && { authDiscrepancy: true }) };
|
|
534
554
|
}
|
|
535
|
-
|
|
555
|
+
if (res.status !== 0) return fail('model-error', 'local model exited unsuccessfully');
|
|
556
|
+
if (decoded.malformed) return fail('model-error', mode === 'judge' ? 'local judge returned malformed JSON wrapper' : 'local model returned malformed JSON wrapper');
|
|
557
|
+
if (decoded.unsuccessful) return fail('model-error', 'local model returned no successful result');
|
|
558
|
+
return { ok: true, text: decoded.wrapper.result, usage: normalizeJudgeUsage(decoded.wrapper.usage), reason: null,
|
|
559
|
+
...meta, identity: wrapperIdentity(decoded.wrapper, cliVersion) };
|
|
536
560
|
}
|
|
537
561
|
|
|
538
562
|
/**
|
|
@@ -557,75 +581,7 @@ function normalizeJudgeUsage(rawUsage) {
|
|
|
557
581
|
|
|
558
582
|
/** mode:'judge' — binary anchored-dedup decision. Argv byte-identical to pre-move plus
|
|
559
583
|
* the same --setting-sources '' isolation the extraction spawn above gains (0.9.15). */
|
|
560
|
-
function runJudgeMode(opts) {
|
|
561
|
-
const spawnSyncImpl = typeof opts.spawnSyncImpl === 'function' ? opts.spawnSyncImpl : spawnSync;
|
|
562
|
-
const bin = typeof opts.claudeBin === 'string' ? opts.claudeBin : resolveClaudeBin(opts);
|
|
563
|
-
if (cachedSettingSourcesUnsupported) return settingSourcesIsolationUnsupportedResult('unknown');
|
|
564
|
-
const prompt = typeof opts.prompt === 'string' ? opts.prompt : '';
|
|
565
|
-
const argv = JUDGE_MODE_ARGV;
|
|
566
|
-
const cliVersion = getClaudeCliVersion(bin, opts);
|
|
567
|
-
let res;
|
|
568
|
-
try {
|
|
569
|
-
res = spawnSyncImpl(bin, argv, {
|
|
570
|
-
input: prompt,
|
|
571
|
-
encoding: 'utf8',
|
|
572
|
-
env: claudeChildEnv(),
|
|
573
|
-
timeout: opts.timeoutMs || 120000,
|
|
574
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
575
|
-
});
|
|
576
|
-
} catch (error) {
|
|
577
|
-
return { ok: false, text: '', usage: null, reason: `judge spawn failed (${bin}): ${error.message}`, reasonCode: 'unknown', authStatus: 'unknown', argv, cliVersion };
|
|
578
|
-
}
|
|
579
|
-
if (!res) {
|
|
580
|
-
return { ok: false, text: '', usage: null, reason: `judge spawn failed (${bin}): no process result`, reasonCode: 'unknown', authStatus: 'unknown', argv, cliVersion };
|
|
581
|
-
}
|
|
582
|
-
if (looksLikeUnsupportedSettingSourcesFlag(res)) {
|
|
583
|
-
cachedSettingSourcesUnsupported = true;
|
|
584
|
-
return { ...settingSourcesIsolationUnsupportedResult('unknown'), argv, cliVersion };
|
|
585
|
-
}
|
|
586
|
-
const stdout = String(res.stdout || '');
|
|
587
|
-
if (res.error) {
|
|
588
|
-
return { ok: false, text: '', usage: null, reason: `judge spawn failed (${bin}): ${res.error.message}`, reasonCode: 'unknown', authStatus: 'unknown', argv, cliVersion };
|
|
589
|
-
}
|
|
590
|
-
const mcpRefusal = enterpriseMcpRefusal(res, 'unknown');
|
|
591
|
-
if (mcpRefusal) return { ...mcpRefusal, argv, cliVersion };
|
|
592
|
-
if (/Please run \/login|authentication_error|401/i.test(stdout) || /Please run \/login|authentication_error/i.test(String(res.stderr || ''))) {
|
|
593
|
-
return { ok: false, text: '', usage: null, reason: 'local judge model is not authenticated', reasonCode: 'cli-unauthenticated', authStatus: 'unknown', argv, cliVersion };
|
|
594
|
-
}
|
|
595
|
-
if (res.status !== 0) {
|
|
596
|
-
return {
|
|
597
|
-
ok: false,
|
|
598
|
-
text: '',
|
|
599
|
-
usage: null,
|
|
600
|
-
reason: `local judge exited ${res.status}: ${(stdout || String(res.stderr || '')).slice(0, 160)}`,
|
|
601
|
-
reasonCode: 'model-error',
|
|
602
|
-
authStatus: 'unknown',
|
|
603
|
-
argv,
|
|
604
|
-
cliVersion,
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
let wrapper;
|
|
608
|
-
try {
|
|
609
|
-
wrapper = JSON.parse(stdout);
|
|
610
|
-
} catch {
|
|
611
|
-
// Some CLI builds append a non-JSON MCP SDK diagnostic to stdout. Recover
|
|
612
|
-
// only a single result value; an extra JSON value of ANY type is ambiguous.
|
|
613
|
-
const values = [];
|
|
614
|
-
for (const rawLine of stdout.split('\n')) {
|
|
615
|
-
const line = rawLine.trim();
|
|
616
|
-
if (!line) continue;
|
|
617
|
-
try { values.push(JSON.parse(line)); } catch { /* discard non-JSON noise */ }
|
|
618
|
-
}
|
|
619
|
-
if (values.length !== 1 || !values[0] || typeof values[0] !== 'object' || values[0].type !== 'result') {
|
|
620
|
-
return { ok: false, text: '', usage: null, reason: 'local judge returned malformed JSON wrapper', reasonCode: 'model-error', authStatus: 'unknown', argv, cliVersion };
|
|
621
|
-
}
|
|
622
|
-
[wrapper] = values;
|
|
623
|
-
}
|
|
624
|
-
if (!wrapper || typeof wrapper.result !== 'string' || wrapper.is_error === true) {
|
|
625
|
-
return { ok: false, text: '', usage: null, reason: 'local judge returned no successful result', reasonCode: 'model-error', authStatus: 'unknown', argv, cliVersion };
|
|
626
|
-
}
|
|
627
|
-
return { ok: true, text: wrapper.result, usage: normalizeJudgeUsage(wrapper.usage), reason: null, authStatus: 'unknown', argv, cliVersion };
|
|
628
|
-
}
|
|
584
|
+
function runJudgeMode(opts) { return runCliMode(opts, 'judge'); }
|
|
629
585
|
|
|
630
586
|
/**
|
|
631
587
|
* runModel(opts) — the provider.interface.js contract. Checks the billing-helper
|
|
@@ -633,7 +589,7 @@ function runJudgeMode(opts) {
|
|
|
633
589
|
* its existing pre-spawn auth short-circuit, mode:'judge' spawns directly as it
|
|
634
590
|
* always has (auth failure there is only knowable post-hoc, from the output).
|
|
635
591
|
*/
|
|
636
|
-
|
|
592
|
+
function runModelImpl(opts) {
|
|
637
593
|
if (detectBillingHelperConfigured(opts)) {
|
|
638
594
|
return {
|
|
639
595
|
ok: false,
|
|
@@ -641,6 +597,7 @@ async function runModel(opts = {}) {
|
|
|
641
597
|
usage: null,
|
|
642
598
|
reason: 'a foreign-billing CLI helper is configured — extraction declines to run under it',
|
|
643
599
|
reasonCode: 'cli-billing-helper-configured',
|
|
600
|
+
refusal: 'pre-invocation',
|
|
644
601
|
authStatus: 'unknown',
|
|
645
602
|
};
|
|
646
603
|
}
|
|
@@ -648,6 +605,11 @@ async function runModel(opts = {}) {
|
|
|
648
605
|
return mode === 'judge' ? runJudgeMode(opts) : runExtractMode(opts);
|
|
649
606
|
}
|
|
650
607
|
|
|
608
|
+
async function runModel(opts = {}) {
|
|
609
|
+
try { return runModelImpl(opts); }
|
|
610
|
+
catch { return { ok: false, text: '', usage: null, reasonCode: 'unknown', reason: 'local model invocation failed', authStatus: 'unknown' }; }
|
|
611
|
+
}
|
|
612
|
+
|
|
651
613
|
/**
|
|
652
614
|
* Legacy-shaped synchronous entry point — extractWithClaudeCode(transcript, opts).
|
|
653
615
|
* Kept (not deleted) because test/ext-0806b-silent-skip.test.js imports it
|
|
@@ -663,6 +625,7 @@ function extractWithClaudeCode(transcript, opts = {}) {
|
|
|
663
625
|
out: '',
|
|
664
626
|
reason: 'a foreign-billing CLI helper is configured — extraction declines to run under it',
|
|
665
627
|
reasonCode: 'cli-billing-helper-configured',
|
|
628
|
+
refusal: 'pre-invocation',
|
|
666
629
|
authStatus: 'unknown',
|
|
667
630
|
};
|
|
668
631
|
}
|