auxilo-mcp 0.9.27 → 0.9.28

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/mcp-server.js CHANGED
@@ -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.27' },
201
+ { name: 'auxilo', version: '0.9.28' },
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auxilo-mcp",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
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",
@@ -516,6 +516,10 @@ function wrapperIdentity(wrapper, cliVersion) {
516
516
  ...(observed === null && { identity_unresolved: models.length ? 'ambiguous' : 'missing' }) };
517
517
  }
518
518
 
519
+ // Exact production CLI wording (verified 2026-09-27..10-01). Add another alternative only for
520
+ // another CONFIRMED real CLI message, never a guessed synonym.
521
+ const OAUTH_EXPIRED_PATTERN = /Failed to authenticate:\s*OAuth session expired and could not be refreshed/i;
522
+
519
523
  function runCliMode(opts, mode) {
520
524
  const spawn = typeof opts.spawnSyncImpl === 'function' ? opts.spawnSyncImpl : spawnSync;
521
525
  const bin = typeof opts.claudeBin === 'string' ? opts.claudeBin : resolveClaudeBin(opts);
@@ -552,6 +556,14 @@ function runCliMode(opts, mode) {
552
556
  if ((!decoded.wrapper && authPattern.test(stdout)) || authPattern.test(String(res.stderr || ''))) {
553
557
  return { ...fail('cli-unauthenticated', authReason), ...(authStatus === 'logged-in' && { authDiscrepancy: true }) };
554
558
  }
559
+ // EXT-0806c: an expired, unrefreshable OAuth session exits nonzero with this exact CLI
560
+ // message on either stream. Nonzero exit only, and stdout only when no result wrapper
561
+ // decoded, so a successful run whose own text discusses an auth bug never matches. Like
562
+ // the check above it is a post-spawn outcome: no `refusal`, so it never permits fallback.
563
+ if (Number.isInteger(res.status) && res.status !== 0
564
+ && ((!decoded.wrapper && OAUTH_EXPIRED_PATTERN.test(stdout)) || OAUTH_EXPIRED_PATTERN.test(String(res.stderr || '')))) {
565
+ return { ...fail('cli-unauthenticated', authReason), ...(authStatus === 'logged-in' && { authDiscrepancy: true }) };
566
+ }
555
567
  if (res.status !== 0) return fail('model-error', 'local model exited unsuccessfully');
556
568
  if (decoded.malformed) return fail('model-error', mode === 'judge' ? 'local judge returned malformed JSON wrapper' : 'local model returned malformed JSON wrapper');
557
569
  if (decoded.unsuccessful) return fail('model-error', 'local model returned no successful result');
@@ -676,4 +688,5 @@ module.exports = {
676
688
  getClaudeCliVersion,
677
689
  looksLikeUnsupportedSettingSourcesFlag,
678
690
  _resetSettingSourcesCacheForTests,
691
+ OAUTH_EXPIRED_PATTERN,
679
692
  };
@@ -366,6 +366,7 @@ async function runModel(opts = {}) {
366
366
  const order = [record.route, ...AUTOMATIC_PROVIDER_ORDER.filter(id =>
367
367
  (id !== record.route || record.route === 'byo-key') && (id !== 'byo-key' || configuredByo))];
368
368
  const attempts = [];
369
+ let firstFailure = null;
369
370
  for (const id of order) {
370
371
  if (id !== store.record.route) {
371
372
  const updated = store.mutate(routeMetadata(id, opts, 'auto'));
@@ -394,6 +395,7 @@ async function runModel(opts = {}) {
394
395
  if (standalone) result = { ...result, ...finishJob(context) };
395
396
  return result;
396
397
  }
398
+ if (!attempts.length) firstFailure = result;
397
399
  attempts.push(id + '=' + (NON_RETRYABLE_FOR_THIS_PROVIDER.has(result.reasonCode) ? result.reasonCode : 'unavailable'));
398
400
  if (result.refusal !== 'pre-invocation' || !store.canFallback()) {
399
401
  const release = store.hold('pinned-route-unusable');
@@ -405,7 +407,13 @@ async function runModel(opts = {}) {
405
407
  const released = store.hold('pinned-route-unusable');
406
408
  context.hold = released.hold || 'pinned-route-unusable';
407
409
  if (!configuredByo && !attempts.some(attempt => attempt.startsWith('byo-key='))) attempts.push('byo-key=provider-not-configured');
408
- return { ok: false, text: '', usage: null, authStatus: 'unknown', reasonCode: 'no-usable-provider',
410
+ // EXT-0806c: a generic 'no-usable-provider' reads as UNKNOWN downstream and never alerts. When the
411
+ // first-choice provider was refused for an unauthenticated CLI, the chain already knows the
412
+ // actionable cause, so surface it; the attempts list stays in `reason`. Read from the first
413
+ // attempt's result object, never from the attempts strings.
414
+ const unauthenticated = Boolean(firstFailure) && firstFailure.reasonCode === 'cli-unauthenticated';
415
+ return { ok: false, text: '', usage: null, authStatus: unauthenticated ? 'logged-out' : 'unknown',
416
+ reasonCode: unauthenticated ? 'cli-unauthenticated' : 'no-usable-provider',
409
417
  reason: 'no usable extraction provider (tried: ' + attempts.join('; ') + ')', hold: context.hold };
410
418
  }
411
419
 
package/scripts/runner.js CHANGED
@@ -364,10 +364,11 @@ async function finalizeExtractionRun(outcomes, opts = {}) {
364
364
  : loadExtractionSkipState(opts);
365
365
  if (rows.length === 0) return state;
366
366
 
367
- // A completed real result or a concrete model/API failure proves the local
368
- // model was attempted. Either resets both visibility streaks.
369
- const resetsAuthStreak = rows.some((row) => !isSkippedExtraction(row)
370
- || row.reasonCode === 'model-error');
367
+ // Only a completed real extraction (a row that is not a skipped extraction)
368
+ // resets both visibility streaks. A 'model-error' skip is counted as an
369
+ // unknown skip, never a reset: an expired-OAuth outage once classified as
370
+ // model-error reset its own alarm on every sweep (EXT-0806c).
371
+ const resetsAuthStreak = rows.some((row) => !isSkippedExtraction(row));
371
372
  if (resetsAuthStreak) {
372
373
  const reset = {
373
374
  ...zeroExtractionSkipState(),
@@ -383,8 +384,7 @@ async function finalizeExtractionRun(outcomes, opts = {}) {
383
384
  const hasUnauthenticated = rows.some((row) => isSkippedExtraction(row)
384
385
  && row.reasonCode === 'cli-unauthenticated');
385
386
  if (!hasUnauthenticated) {
386
- const hasUnknown = rows.some((row) => isSkippedExtraction(row)
387
- && row.reasonCode !== 'model-error');
387
+ const hasUnknown = rows.some((row) => isSkippedExtraction(row));
388
388
  if (!hasUnknown) return state;
389
389
  const unknown = {
390
390
  ...state,
@@ -1066,6 +1066,8 @@ function installDigest() {
1066
1066
 
1067
1067
  // ─── Status (B14) ───────────────────────────────────────────────────────────
1068
1068
 
1069
+ const EXTRACTION_STALE_MS = 48 * 60 * 60 * 1000;
1070
+
1069
1071
  function renderExtractionStatus(opts = {}) {
1070
1072
  const state = normalizeExtractionSkipState(opts.state) || zeroExtractionSkipState();
1071
1073
  const ledger = opts.ledger && typeof opts.ledger === 'object' ? opts.ledger : {};
@@ -1086,19 +1088,28 @@ function renderExtractionStatus(opts = {}) {
1086
1088
 
1087
1089
  const timestamp = ledger.lastRealExtractionAt;
1088
1090
  if (typeof timestamp === 'string' && isCanonicalIsoTimestamp(timestamp)) {
1091
+ const rawNow = typeof opts.now === 'function' ? opts.now() : opts.now;
1092
+ const nowMs = rawNow === undefined ? Date.now()
1093
+ : rawNow instanceof Date ? rawNow.getTime()
1094
+ : typeof rawNow === 'string' ? Date.parse(rawNow) : Number(rawNow);
1095
+ const ageMs = nowMs - Date.parse(timestamp);
1096
+ if (Number.isFinite(ageMs) && ageMs > EXTRACTION_STALE_MS) {
1097
+ return `Extraction: STALE (no real extraction for ${Math.floor(ageMs / 3600000)}h, last ${timestamp}). ` +
1098
+ 'Run `claude auth login` and check extract.log';
1099
+ }
1089
1100
  return `Extraction: OK (last real extraction ${timestamp})`;
1090
1101
  }
1091
1102
  return 'Extraction: OK (last real extraction unavailable)';
1092
1103
  }
1093
1104
 
1094
- function currentExtractionStatusLine(ledger) {
1105
+ function currentExtractionStatusLine(ledger, now) {
1095
1106
  const state = loadExtractionSkipState();
1096
1107
  let authStatus = 'unknown';
1097
1108
  try {
1098
1109
  const { checkClaudeAuthStatus } = require('./extract-local.js');
1099
1110
  authStatus = checkClaudeAuthStatus();
1100
1111
  } catch { /* missing/broken local CLI state is UNKNOWN, never authenticated */ }
1101
- return renderExtractionStatus({ state, ledger, authStatus });
1112
+ return renderExtractionStatus({ state, ledger, authStatus, now });
1102
1113
  }
1103
1114
 
1104
1115
  async function printStatus() {
@@ -1146,7 +1157,7 @@ async function printStatus() {
1146
1157
  console.log(`Pending queue: ${pendingCount} file(s)`);
1147
1158
 
1148
1159
  // 7. Extraction health — durable skip state plus the authoritative CLI auth probe.
1149
- console.log(currentExtractionStatusLine(ledger));
1160
+ console.log(currentExtractionStatusLine(ledger, Date.now()));
1150
1161
  }
1151
1162
 
1152
1163
  // ─── Scrub + Verify ─────────────────────────────────────────────────────────