mixdog 0.9.8 → 0.9.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.8",
3
+ "version": "0.9.10",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -6,29 +6,27 @@ kind: retrieval
6
6
 
7
7
  # Role: explorer
8
8
 
9
- You are a one-shot locator, not a researcher. You find WHERE, never WHY or
10
- HOW: no reading code to understand it, no explaining behaviour, no tracing
11
- logic across files. Coordinates are the entire deliverable.
9
+ You are a one-shot locator, not a researcher. You find WHERE, never WHY
10
+ no understanding code, explaining behaviour, or tracing logic. Coordinates
11
+ are the entire deliverable.
12
12
 
13
- Default shape: ONE tool turn, then the answer. Turn 1 is ONE maximal batch:
14
- a single `grep` carrying ALL literal strings/errors/identifier guesses
15
- (content mode, small `-C`), plus `code_graph`/`find`/`glob` in the same
16
- turn. Fire everything at once; never probe one tool at a time. Any second
17
- turn means turn 1 under-batched.
13
+ Default shape: ONE maximal-batch turn, then the answer. Fire everything at
14
+ once: a single `grep` whose pattern[] carries ALL literal strings/errors/
15
+ identifier guesses synonyms, casings, variants — plus `code_graph`/`find`/
16
+ `glob` in the SAME turn when useful. Width is free, turns are not.
18
17
 
19
- Answer straight from search output: grep/code_graph hits already carry
20
- `path:line` — NEVER spend a turn on `read` just to confirm or polish an
21
- anchor you already have. Prefer user-visible strings and identifiers.
22
- `dist`/generated hits are leads, not answers: trace them back to source.
18
+ Answer straight from search output: hits already carry `path:line` — never
19
+ spend a turn on `read` to confirm or polish an anchor. `dist`/generated
20
+ hits are leads, not answers: trace them to source.
23
21
 
24
- Budget: 5 tool turns; coordinates only, zero analysis. The FIRST
25
- credible `path:line` ends the search — answer in that same turn; do not
26
- verify, refine, or collect extras. A plausible anchor now beats a perfect
27
- anchor later: mark weak ones `?` instead of checking them. Turns 2-5 exist
28
- ONLY for a zero-anchor turn 1: derive new tokens from returned paths/names
29
- or widen scope (no repeated synonyms), batching ALL remaining candidates in
30
- one shot. Past turn 5 you are wasting the caller's time: answer with
31
- best-so-far anchors or `EXPLORATION_FAILED`.
22
+ Budget: 5 turns of maximal batches. Credible `path:line` on screen means the
23
+ search is OVER — answer in that turn; no verifying or collecting extras;
24
+ mark weak anchors `?`. Turns 2-5 exist ONLY for a zero-anchor turn 1: derive
25
+ new tokens from returned paths/names or widen scope, packing ALL candidates
26
+ into the next batch. Concept queries: grep implementation vocabulary
27
+ (file/dir names, symbol fragments, config keys). `EXPLORATION_FAILED` only
28
+ after all 5 turns found zero anchors giving up early is as wrong as
29
+ over-searching; at turn 5 answer best-so-far.
32
30
 
33
31
  Answer format, nothing else:
34
32
  - up to 5 lines: `path:line — symbol/name — short reason` (append `?` if weak)
@@ -254,6 +254,15 @@ function _codexMetadataBase(entry, { poolKey, cacheKey, sendOpts } = {}) {
254
254
  turn_id: turnId,
255
255
  window_id: windowId,
256
256
  request_kind: requestKind,
257
+ // Richer codex turn-metadata fields (responses_metadata.rs:264-280:
258
+ // thread_source "user" per protocol.rs:2751-2765, sandbox label).
259
+ // A/B 2026-07-04 (rvA/rvB interleaved, 24 sessions/arm): no effect
260
+ // (it2 full 3 vs 2, miss 2 vs 3 — noise). Default OFF; keep the knob
261
+ // for future probes if the backend starts gating on payload richness.
262
+ ...(process.env.MIXDOG_OAI_TURN_METADATA_RICH === '1' ? {
263
+ thread_source: 'user',
264
+ sandbox: 'read-only',
265
+ } : {}),
257
266
  turn_started_at_unix_ms: startedAt,
258
267
  };
259
268
  const metadata = {
@@ -54,6 +54,52 @@ const MAX_POOLED_SOCKETS_PER_KEY = 8;
54
54
  // closing, ephemeral }
55
55
  const _wsPool = new Map();
56
56
 
57
+ // --- Cache-route probe state (2026-07-04 hunt) -----------------------------
58
+ // CF cookie stickiness (codex chatgpt_cloudflare_cookies.rs:22-55 persists
59
+ // __cf_bm/_cfuvid across HTTP clients; our WS handshakes never echo them, so
60
+ // Cloudflare may re-shard every fresh socket). Jar is per-process, keyed by
61
+ // auth account. Env knobs (A/B):
62
+ // MIXDOG_OAI_CF_COOKIES=1 capture Set-Cookie from the 101 upgrade and
63
+ // send Cookie on subsequent handshakes
64
+ // MIXDOG_OAI_SESSION_AFFINITY=1 send x-session-affinity: <cacheKey>
65
+ // (opencode request.ts:187, ws-pool.ts:66)
66
+ // MIXDOG_OAI_WS_URL_SESSION=0 drop the ?session_id= URL query (codex/pi/
67
+ // opencode all use the bare WS URL)
68
+ const _cfCookieJar = new Map(); // accountKey -> { name -> value }
69
+ const _CF_COOKIE_ALLOWLIST = new Set(['__cf_bm', '_cfuvid']);
70
+
71
+ function _envOn(name) {
72
+ const v = String(process.env[name] || '').trim().toLowerCase();
73
+ return ['1', 'true', 'yes', 'on'].includes(v);
74
+ }
75
+
76
+ function _cfCookieAccountKey(auth) {
77
+ return String(auth?.account_id || auth?.apiKey || 'default');
78
+ }
79
+
80
+ function _cfCookieHeader(auth) {
81
+ if (!_envOn('MIXDOG_OAI_CF_COOKIES')) return null;
82
+ const jar = _cfCookieJar.get(_cfCookieAccountKey(auth));
83
+ if (!jar || !jar.size) return null;
84
+ return [...jar.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
85
+ }
86
+
87
+ function _cfCookieCapture(auth, setCookieHeaders) {
88
+ if (!_envOn('MIXDOG_OAI_CF_COOKIES')) return;
89
+ const list = Array.isArray(setCookieHeaders) ? setCookieHeaders : (setCookieHeaders ? [setCookieHeaders] : []);
90
+ if (!list.length) return;
91
+ const key = _cfCookieAccountKey(auth);
92
+ let jar = _cfCookieJar.get(key);
93
+ if (!jar) { jar = new Map(); _cfCookieJar.set(key, jar); }
94
+ for (const raw of list) {
95
+ const pair = String(raw || '').split(';', 1)[0];
96
+ const eq = pair.indexOf('=');
97
+ if (eq <= 0) continue;
98
+ const name = pair.slice(0, eq).trim();
99
+ if (_CF_COOKIE_ALLOWLIST.has(name)) jar.set(name, pair.slice(eq + 1).trim());
100
+ }
101
+ }
102
+
57
103
  function _getPoolArr(poolKey) {
58
104
  if (!poolKey) return null;
59
105
  let arr = _wsPool.get(poolKey);
@@ -200,6 +246,14 @@ function _buildHandshakeHeaders({ auth, sessionToken, turnState, cacheKey: _cach
200
246
  headers['x-client-request-id'] = randomBytes(16).toString('hex');
201
247
  }
202
248
  if (turnState) headers['x-codex-turn-state'] = turnState;
249
+ // Probe knobs (cache-route hunt 2026-07-04): see jar block at top of file.
250
+ if (isOpenAiOauth) {
251
+ const jar = _cfCookieHeader(auth);
252
+ if (jar) headers['Cookie'] = jar;
253
+ if (_envOn('MIXDOG_OAI_SESSION_AFFINITY') && (_cacheKey || sessionToken)) {
254
+ headers['x-session-affinity'] = String(_cacheKey || sessionToken);
255
+ }
256
+ }
203
257
  return headers;
204
258
  }
205
259
 
@@ -225,7 +279,17 @@ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey,
225
279
  if (process.env.MIXDOG_DEBUG_AGENT) {
226
280
  process.stderr.write(`[agent-trace] ws-open-start url=${baseUrl} tokenHash=${createHash('sha256').update(String(sessionToken)).digest('hex').slice(0, 8)} ts=${_wsOpenStart}\n`);
227
281
  }
228
- const url = baseUrl + (sessionToken ? `?session_id=${encodeURIComponent(String(sessionToken))}` : '');
282
+ // Bare WS URL by default (codex/pi/opencode parity). Interleaved A/B
283
+ // (2026-07-04, ivA/ivB, 24 sessions each, alternating rounds to cancel
284
+ // server-time noise): dropping the ?session_id= query improved it1
285
+ // warmup-prefix hits 15/24 -> 22/24 and it2 full hits 11 -> 15 (miss
286
+ // 5 -> 4). The query string seeds CF/backend shard routing away from
287
+ // the header-affine cache node; session identity still rides on the
288
+ // session_id/session-id handshake headers. Re-enable the legacy query
289
+ // form with MIXDOG_OAI_WS_URL_SESSION=1.
290
+ const url = baseUrl + (sessionToken && process.env.MIXDOG_OAI_WS_URL_SESSION === '1'
291
+ ? `?session_id=${encodeURIComponent(String(sessionToken))}`
292
+ : '');
229
293
  return new Promise((resolve, reject) => {
230
294
  let settled = false;
231
295
  let abortListener = null;
@@ -260,6 +324,7 @@ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey,
260
324
  try {
261
325
  const ts = res?.headers?.['x-codex-turn-state'];
262
326
  if (typeof ts === 'string' && ts.length) capturedHeaders.turnState = ts;
327
+ _cfCookieCapture(auth, res?.headers?.['set-cookie']);
263
328
  // Probe: dump the full 101-upgrade response header set so we can
264
329
  // see what the server actually issues (turn-state investigation).
265
330
  if (process.env.MIXDOG_WS_UPGRADE_HEADER_PROBE) {
@@ -15,7 +15,7 @@ export const EXPLORE_TOOL = {
15
15
  name: 'explore',
16
16
  title: 'Explore',
17
17
  annotations: { title: 'Explore', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
18
- description: 'Repo anchor locator. LLM-backed; broad/uncertain only. Array only for independent targets.',
18
+ description: 'Repo anchor locator: broad/uncertain targets, no known path. Array = independent targets.',
19
19
  inputSchema: {
20
20
  type: 'object',
21
21
  properties: {
@@ -70,7 +70,7 @@ function escapeXml(str) {
70
70
  // reminder. The full no-verdict contract lives at system level
71
71
  // (rules/agent/30-explorer.md).
72
72
  export function buildExplorerPrompt(query) {
73
- return `<query>${escapeXml(query)}</query>\nReminder: coordinates only — WHERE, never WHY. Finish within 5 tool turns, ideally 1. Turn 1 MUST be one maximal batch: fire EVERY candidate lookup at once (grep with all token variants + code_graph + find/glob in the same turn); never one tool at a time. Answer straight from search output — grep/code_graph hits already carry path:line; never spend a turn reading to confirm, polish, or understand. The FIRST credible anchor ends the search: STOP and answer in that same turn, marking weak anchors with ?. By turn 5 answer with best-so-far or EXPLORATION_FAILED. Output only anchor lines formatted as path:line — symbol/name — short reason, or EXPLORATION_FAILED. No preamble, bullets, numbering, headings, summary, code quotes, analysis, verdicts, ratings, or recommendations.`;
73
+ return `<query>${escapeXml(query)}</query>\nReminder: coordinates only — WHERE, never WHY. Budget: 5 turns of maximal batches, ideally 1. Turn 1 fires everything at once: one grep whose pattern[] packs ALL token variants, plus code_graph/find/glob in the SAME turn when useful width is free, turns are not. Answer straight from search output — hits already carry path:line; never read to confirm, polish, or understand. Before every call ask: is a credible anchor already on screen? If yes STOP and answer now, marking weak anchors with ?. EXPLORATION_FAILED only after all 5 turns found zero anchors; at turn 5 answer with best-so-far. Output only anchor lines formatted as path:line — symbol/name — short reason, or EXPLORATION_FAILED. No preamble, bullets, numbering, headings, summary, code quotes, analysis, verdicts, ratings, or recommendations.`;
74
74
  }
75
75
 
76
76
  export function normalizeExploreQueries(rawQuery) {