auxilo-mcp 0.9.8 → 0.9.9

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
@@ -197,7 +197,7 @@ async function postBulkChunks(headers, decisions) {
197
197
  }
198
198
 
199
199
  const server = new Server(
200
- { name: 'auxilo', version: '0.9.8' },
200
+ { name: 'auxilo', version: '0.9.9' },
201
201
  {
202
202
  capabilities: { tools: {} },
203
203
  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.8",
3
+ "version": "0.9.9",
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",
@@ -558,11 +558,14 @@ async function runAnchoredJudge(candidates, indexState, opts = {}) {
558
558
 
559
559
  /**
560
560
  * Extract learnings locally. Returns { learnings: [...] } or { learnings: [], skipped }.
561
- * Only claude-code has a local extractor today; other clients rely on the agent's
562
- * proactive auxilo_contribute (MCP) call.
561
+ * Claude Code and Codex rollout captures use the existing client-local Claude
562
+ * extractor; other clients rely on the agent's proactive auxilo_contribute
563
+ * (MCP) call.
563
564
  */
565
+ const EXTRACTABLE_SOURCES = new Set(['claude-code', 'codex-cli']);
566
+
564
567
  async function extractLocally(transcript, sourceType, opts = {}) {
565
- if (sourceType && sourceType !== 'claude-code') {
568
+ if (sourceType && !EXTRACTABLE_SOURCES.has(sourceType)) {
566
569
  return { learnings: [], skipped: `local extraction not implemented for "${sourceType}" — agent contributes via auxilo_contribute` };
567
570
  }
568
571
 
package/scripts/runner.js CHANGED
@@ -973,6 +973,7 @@ async function main() {
973
973
  let totalOversize = 0; // N1: oversize-cap skips (subset of totalSkipped)
974
974
  let totalFailed = 0;
975
975
  let totalHeld = 0;
976
+ const refusedBySource = new Map();
976
977
 
977
978
  for (const source of sources) {
978
979
  log(`[runner] Discovering sessions from ${source.label} (${source.type})...`);
@@ -1021,7 +1022,7 @@ async function main() {
1021
1022
 
1022
1023
  // UC-1 format-probe refusal: null = skip silently (not a failure).
1023
1024
  if (!transcriptData || typeof transcriptData.transcript !== 'string') {
1024
- log(`[runner] Skipped — format probe refused (source=${source.type})`);
1025
+ refusedBySource.set(source.type, (refusedBySource.get(source.type) || 0) + 1);
1025
1026
  totalSkipped++;
1026
1027
  ledgerMark(ledger, source.type, sessionRef.sessionId, 'probe-refused', sessionRef.mtime);
1027
1028
  continue;
@@ -1098,6 +1099,9 @@ async function main() {
1098
1099
  }
1099
1100
  }
1100
1101
 
1102
+ for (const [sourceType, count] of refusedBySource) {
1103
+ log(`[runner] ${sourceType}: ${count} refused (non-user/format)`);
1104
+ }
1101
1105
  saveLedger(ledger);
1102
1106
  log(`[runner] Summary: ${totalDiscovered} discovered, ${totalProcessed} processed, ${totalSkipped} skipped (${totalOversize} oversize), ${totalFailed} failed`);
1103
1107
  if (totalOversize > 0) {
@@ -0,0 +1,268 @@
1
+ /**
2
+ * scripts/sources/codex-cli.js — Codex CLI + Desktop Transcript Source (UC-6)
3
+ *
4
+ * Best-effort UC-3 poll adapter.
5
+ * UC-3 disclaimer: format community-reverse-engineered; verified against live operator install 2026-07-26 (247 rollouts).
6
+ * Shape drift fails silent instead of producing a guessed transcript.
7
+ *
8
+ * Upstream context:
9
+ * openai/codex#21639 — Codex Desktop hooks regression
10
+ * openai/codex#24948 — rollout files can grow to multi-GB size
11
+ * openai/codex#21660 — rollout files may be created with 0644 permissions
12
+ *
13
+ * Desktop embeds the CLI and shares its ~/.codex rollout store, so one source
14
+ * id deliberately covers both clients. Privacy-sensitive base instructions,
15
+ * world state (including AGENTS.md), reasoning, and duplicate event messages
16
+ * are never normalized into transcript output.
17
+ *
18
+ * @module sources/codex-cli
19
+ */
20
+
21
+ 'use strict';
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const os = require('os');
26
+ const { TranscriptSource } = require('./source.interface');
27
+
28
+ const DEFAULT_QUIESCENCE_MS = 30 * 60 * 1000;
29
+ const UUID_AT_END_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
30
+
31
+ function resolveQuiescenceMs(env = process.env) {
32
+ const raw = env && env.AUXILO_CODEX_QUIESCENCE_MS;
33
+ if (raw === undefined || raw === null || raw === '') return DEFAULT_QUIESCENCE_MS;
34
+ const value = Number(raw);
35
+ return Number.isInteger(value) && value > 0 ? value : DEFAULT_QUIESCENCE_MS;
36
+ }
37
+
38
+ function rolloutSessionId(filePath) {
39
+ const name = path.basename(filePath);
40
+ const match = name.match(UUID_AT_END_RE);
41
+ return match ? match[1] : path.basename(name, path.extname(name));
42
+ }
43
+
44
+ function listRollouts(root, recursive) {
45
+ const found = [];
46
+ let entries;
47
+ try {
48
+ entries = fs.readdirSync(root, { withFileTypes: true });
49
+ } catch {
50
+ return found;
51
+ }
52
+ for (const entry of entries) {
53
+ const filePath = path.join(root, entry.name);
54
+ if (recursive && entry.isDirectory()) {
55
+ found.push(...listRollouts(filePath, true));
56
+ continue;
57
+ }
58
+ if (entry.isFile() && /^rollout-.*\.jsonl$/i.test(entry.name)) found.push(filePath);
59
+ }
60
+ return found;
61
+ }
62
+
63
+ function textContentItems(content) {
64
+ if (!Array.isArray(content)) return [];
65
+ return content
66
+ .filter((item) =>
67
+ item &&
68
+ typeof item === 'object' &&
69
+ ['input_text', 'output_text', 'text'].includes(item.type) &&
70
+ typeof item.text === 'string'
71
+ )
72
+ .map((item) => item.text);
73
+ }
74
+
75
+ function stringifyValue(value) {
76
+ if (value === undefined || value === null) return '';
77
+ if (typeof value === 'string') return value;
78
+ try { return JSON.stringify(value) ?? ''; } catch { return String(value); }
79
+ }
80
+
81
+ function oneLine(value) {
82
+ return stringifyValue(value).replace(/\s+/g, ' ').trim();
83
+ }
84
+
85
+ function toolOutputItems(payload) {
86
+ const output = payload.output !== undefined ? payload.output : payload.content;
87
+ if (typeof output === 'string') return [output];
88
+ if (Array.isArray(output)) {
89
+ return output.flatMap((item) => {
90
+ if (typeof item === 'string') return [item];
91
+ if (!item || typeof item !== 'object') return [];
92
+ if (typeof item.text === 'string') return [item.text];
93
+ if (typeof item.output === 'string') return [item.output];
94
+ if (typeof item.content === 'string') return [item.content];
95
+ return [];
96
+ });
97
+ }
98
+ if (output && typeof output === 'object') {
99
+ if (typeof output.text === 'string') return [output.text];
100
+ if (typeof output.output === 'string') return [output.output];
101
+ }
102
+ return [];
103
+ }
104
+
105
+ class CodexCliSource extends TranscriptSource {
106
+ static id = 'codex-cli';
107
+ static displayName = 'Codex (CLI + Desktop)';
108
+ static version = '1.0.0';
109
+
110
+ constructor(config = {}) {
111
+ super(config);
112
+ const homeDir = config.homeDir || os.homedir();
113
+ this.codexDir = config.codexDir || path.join(homeDir, '.codex');
114
+ this.sessionsDir = path.join(this.codexDir, 'sessions');
115
+ this.archivedSessionsDir = path.join(this.codexDir, 'archived_sessions');
116
+ this.env = config.env || process.env;
117
+ }
118
+
119
+ async detect() {
120
+ try {
121
+ return fs.statSync(this.sessionsDir).isDirectory();
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+
127
+ async discoverSessions({ since } = {}) {
128
+ const parsedSince = since ? Date.parse(since) : 0;
129
+ const sinceMs = Number.isFinite(parsedSince) ? parsedSince : 0;
130
+ const quiescentBefore = Date.now() - resolveQuiescenceMs(this.env);
131
+ const candidates = [
132
+ ...listRollouts(this.sessionsDir, true),
133
+ ...listRollouts(this.archivedSessionsDir, false),
134
+ ];
135
+ const sessions = [];
136
+
137
+ for (const filePath of candidates) {
138
+ try {
139
+ const stat = fs.statSync(filePath);
140
+ if (!stat.isFile()) continue;
141
+ if (stat.mtimeMs <= sinceMs || stat.mtimeMs >= quiescentBefore) continue;
142
+ sessions.push({
143
+ sessionId: rolloutSessionId(filePath),
144
+ path: filePath,
145
+ mtime: stat.mtime.toISOString(),
146
+ bytes: stat.size,
147
+ });
148
+ } catch {
149
+ // A rollout can disappear or become unreadable while the sweep walks.
150
+ }
151
+ }
152
+
153
+ return sessions.sort((a, b) =>
154
+ Date.parse(a.mtime) - Date.parse(b.mtime) || a.path.localeCompare(b.path)
155
+ );
156
+ }
157
+
158
+ async readSession(sessionRef) {
159
+ try {
160
+ return await this._readSession(sessionRef);
161
+ } catch {
162
+ // Gate-A F-A: the adapter contract is never-throw. A newly observed
163
+ // optional field or other shape drift refuses the whole rollout rather
164
+ // than escaping into the runner as a failed session.
165
+ return this._refuse(sessionRef && sessionRef.path, 'unexpected normalization error');
166
+ }
167
+ }
168
+
169
+ _readSession(sessionRef) {
170
+ const filePath = sessionRef && sessionRef.path;
171
+ let raw;
172
+ try {
173
+ raw = fs.readFileSync(filePath, 'utf8');
174
+ } catch {
175
+ return this._refuse(filePath, 'unreadable rollout');
176
+ }
177
+
178
+ const records = [];
179
+ for (const line of raw.split(/\r?\n/)) {
180
+ if (!line.trim()) continue;
181
+ try {
182
+ const record = JSON.parse(line);
183
+ if (record && typeof record === 'object') records.push(record);
184
+ } catch {
185
+ // Individual malformed records are ignored; the format probe below
186
+ // still requires a valid first record and at least two valid records.
187
+ }
188
+ }
189
+
190
+ if (records.length === 0 || records[0].type !== 'session_meta') {
191
+ return this._refuse(filePath, 'first parseable record is not session_meta');
192
+ }
193
+ const sessionMeta = records[0].payload;
194
+ if (!sessionMeta || typeof sessionMeta !== 'object') {
195
+ return this._refuse(filePath, 'session_meta payload missing');
196
+ }
197
+ if (sessionMeta.thread_source !== 'user') {
198
+ return this._refuse(filePath, 'non-user thread');
199
+ }
200
+ if (records.length < 2) {
201
+ return this._refuse(filePath, 'fewer than two parseable records');
202
+ }
203
+
204
+ const turns = [];
205
+ for (const record of records) {
206
+ if (record.type !== 'response_item') continue;
207
+ const payload = record.payload;
208
+ if (!payload || typeof payload !== 'object') continue;
209
+
210
+ if (payload.type === 'message') {
211
+ const role = payload.role === 'user'
212
+ ? 'User'
213
+ : payload.role === 'assistant'
214
+ ? 'Assistant'
215
+ : null;
216
+ if (!role) continue;
217
+ const text = textContentItems(payload.content).join('\n').trim();
218
+ if (text) turns.push(`${role}: ${text}`);
219
+ continue;
220
+ }
221
+
222
+ if (payload.type === 'custom_tool_call') {
223
+ const name = oneLine(payload.name || payload.tool_name || payload.tool || 'unknown');
224
+ const args = oneLine(payload.arguments).slice(0, 500);
225
+ turns.push(`Tool: ${name}${args ? ` ${args}` : ''}`);
226
+ continue;
227
+ }
228
+
229
+ if (payload.type === 'custom_tool_call_output') {
230
+ const items = toolOutputItems(payload)
231
+ .map((item) => String(item).slice(0, 2000))
232
+ .filter(Boolean);
233
+ if (items.length > 0) turns.push(`Tool result: ${items.join('\n')}`);
234
+ }
235
+ }
236
+
237
+ return {
238
+ transcript: turns.join('\n\n'),
239
+ metadata: {
240
+ sessionId: sessionRef.sessionId,
241
+ source: 'codex-cli',
242
+ mtime: sessionRef.mtime,
243
+ bytes: sessionRef.bytes,
244
+ originator: sessionMeta.originator,
245
+ cwd: sessionMeta.cwd,
246
+ model_provider: sessionMeta.model_provider,
247
+ },
248
+ };
249
+ }
250
+
251
+ _refuse(filePath, reason) {
252
+ // Gate-A F-B: sweeps summarize refusals once in runner.js. Per-file paths
253
+ // are available only under an explicit local debug opt-in.
254
+ if (this.env.AUXILO_CODEX_DEBUG === '1') {
255
+ process.stderr.write(`[codex-cli] format probe refused ${filePath || '(unknown)'} (${reason}) — skipping\n`);
256
+ }
257
+ return null;
258
+ }
259
+
260
+ async registerSessionEndHook(cb) { return null; }
261
+ }
262
+
263
+ module.exports = {
264
+ CodexCliSource,
265
+ DEFAULT_QUIESCENCE_MS,
266
+ resolveQuiescenceMs,
267
+ rolloutSessionId,
268
+ };