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 +1 -1
- package/package.json +1 -1
- package/scripts/extract-local.js +6 -3
- package/scripts/runner.js +5 -1
- package/scripts/sources/codex-cli.js +268 -0
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.
|
|
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.
|
|
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",
|
package/scripts/extract-local.js
CHANGED
|
@@ -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
|
-
*
|
|
562
|
-
* proactive auxilo_contribute
|
|
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
|
|
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
|
-
|
|
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
|
+
};
|