mixdog 0.9.44 → 0.9.46
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 +5 -2
- package/scripts/agent-dispatch-abort-compose-test.mjs +31 -0
- package/scripts/agent-model-liveness-test.mjs +618 -0
- package/scripts/agent-trace-io-test.mjs +68 -4
- package/scripts/bench-run.mjs +30 -3
- package/scripts/compact-pressure-test.mjs +187 -4
- package/scripts/compact-prior-context-flatten-test.mjs +252 -0
- package/scripts/compact-trigger-migration-smoke.mjs +8 -6
- package/scripts/compacted-placeholder-scrub-test.mjs +20 -34
- package/scripts/context-mcp-metering-test.mjs +75 -0
- package/scripts/explore-prompt-policy-test.mjs +15 -16
- package/scripts/explore-timeout-cancel-test.mjs +345 -0
- package/scripts/headless-pristine-execution-test.mjs +614 -0
- package/scripts/internal-comms-smoke.mjs +15 -6
- package/scripts/live-worker-smoke.mjs +1 -1
- package/scripts/memory-core-input-test.mjs +137 -0
- package/scripts/memory-rule-contract-test.mjs +5 -5
- package/scripts/openai-oauth-refresh-race-test.mjs +120 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +26 -0
- package/scripts/parent-abort-link-test.mjs +22 -0
- package/scripts/provider-toolcall-test.mjs +22 -0
- package/scripts/reactive-compact-persist-smoke.mjs +8 -4
- package/scripts/session-bench.mjs +3 -70
- package/scripts/task-bench.mjs +0 -2
- package/scripts/terminal-bench-isolation-guards-test.mjs +102 -0
- package/scripts/tool-smoke.mjs +21 -21
- package/scripts/tool-tui-presentation-test.mjs +68 -0
- package/scripts/tui-transcript-jitter-harness-entry.jsx +91 -10
- package/src/app.mjs +28 -103
- package/src/cli.mjs +17 -13
- package/src/headless-command.mjs +139 -0
- package/src/headless-role.mjs +121 -10
- package/src/help.mjs +4 -1
- package/src/rules/agent/00-common.md +3 -3
- package/src/rules/agent/00-core.md +8 -9
- package/src/rules/agent/20-skip-protocol.md +2 -3
- package/src/rules/agent/30-explorer.md +50 -56
- package/src/rules/agent/40-cycle1-agent.md +10 -12
- package/src/rules/agent/41-cycle2-agent.md +12 -9
- package/src/rules/agent/42-cycle3-agent.md +4 -6
- package/src/rules/lead/01-general.md +5 -6
- package/src/rules/lead/02-channels.md +1 -1
- package/src/rules/lead/lead-brief.md +14 -17
- package/src/rules/lead/lead-tool.md +3 -3
- package/src/rules/shared/01-tool.md +41 -43
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +46 -10
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +44 -31
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +18 -3
- package/src/runtime/agent/orchestrator/config.mjs +96 -30
- package/src/runtime/agent/orchestrator/context/collect.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +123 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +148 -30
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +5 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +62 -14
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +141 -19
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +85 -17
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -17
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +159 -20
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +83 -10
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +75 -7
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +4 -374
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +0 -75
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +0 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +32 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +86 -15
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +30 -27
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +1 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +21 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -51
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/runtime/memory/index.mjs +0 -1
- package/src/runtime/memory/lib/core-memory-store.mjs +44 -24
- package/src/runtime/memory/lib/http-router.mjs +0 -193
- package/src/runtime/memory/lib/memory-action-handlers.mjs +37 -11
- package/src/runtime/memory/tool-defs.mjs +5 -6
- package/src/runtime/shared/config.mjs +11 -34
- package/src/runtime/shared/pristine-execution-contract.json +75 -0
- package/src/runtime/shared/pristine-execution.mjs +356 -0
- package/src/runtime/shared/provider-api-key.mjs +43 -0
- package/src/runtime/shared/provider-auth-binding.mjs +21 -0
- package/src/runtime/shared/update-checker.mjs +40 -10
- package/src/session-runtime/context-status.mjs +61 -13
- package/src/session-runtime/mcp-glue.mjs +29 -2
- package/src/session-runtime/plugin-mcp.mjs +7 -0
- package/src/session-runtime/resource-api.mjs +38 -5
- package/src/session-runtime/runtime-core.mjs +5 -1
- package/src/session-runtime/session-turn-api.mjs +14 -2
- package/src/session-runtime/settings-api.mjs +5 -0
- package/src/session-runtime/tool-catalog.mjs +13 -2
- package/src/session-runtime/tool-defs.mjs +1 -3
- package/src/standalone/agent-task-status.mjs +50 -11
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/explore-tool.mjs +257 -49
- package/src/standalone/seeds.mjs +1 -0
- package/src/tui/App.jsx +26 -16
- package/src/tui/app/use-transcript-scroll.mjs +4 -3
- package/src/tui/app/use-transcript-window.mjs +12 -21
- package/src/tui/components/ContextPanel.jsx +19 -25
- package/src/tui/dist/index.mjs +89 -78
- package/src/tui/engine/agent-envelope.mjs +16 -5
- package/src/tui/engine/labels.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +21 -51
- package/src/workflows/solo/WORKFLOW.md +12 -17
- package/src/workflows/solo-review/WORKFLOW.md +0 -47
|
@@ -410,55 +410,6 @@ function capToolOutput(result, options = {}) {
|
|
|
410
410
|
return `${head}\n... [tool-output truncated: ${Math.round(bytes / 1024)} KB -> ${Math.round(cap / 1024)} KB cap (tool_output_token_limit)] ...\n${tail}`;
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
const _GREP_SWEEP_NUDGE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_ENABLE_GREP_SWEEP_NUDGE || ''));
|
|
414
|
-
const _GREP_SWEEP_MIN_CALLS = 20;
|
|
415
|
-
const _GREP_SWEEP_MIN_UNIQUE = 16;
|
|
416
|
-
const _grepSweepState = new Map();
|
|
417
|
-
|
|
418
|
-
function _grepSweepTerms(pattern) {
|
|
419
|
-
const raw = Array.isArray(pattern) ? pattern.join('|') : String(pattern || '');
|
|
420
|
-
return [...new Set(raw
|
|
421
|
-
.split(/[|,\s()"'`[\]{}.*+?^$\\:-]+/g)
|
|
422
|
-
.map((s) => s.trim().toLowerCase())
|
|
423
|
-
.filter((s) => s.length >= 4))];
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function _normalizeSweepPath(value) {
|
|
427
|
-
return String(value || '.')
|
|
428
|
-
.replace(/\\/g, '/')
|
|
429
|
-
.replace(/^([A-Za-z]):/, (_, d) => d.toLowerCase() + ':')
|
|
430
|
-
.replace(/\/+/g, '/')
|
|
431
|
-
.replace(/\/$/, '') || '.';
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function _grepSweepKey(args = {}) {
|
|
435
|
-
const path = _normalizeSweepPath(args.path || '.');
|
|
436
|
-
const glob = Array.isArray(args.glob) ? args.glob.join(',') : String(args.glob || '');
|
|
437
|
-
const terms = _grepSweepTerms(args.pattern).slice(0, 3).join('|');
|
|
438
|
-
return `${path}::${glob}::${terms}`;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function _maybeAppendGrepSweepNudge(toolName, args, result, scope) {
|
|
442
|
-
if (!_GREP_SWEEP_NUDGE_ENABLED || toolName !== 'grep' || typeof result !== 'string') return result;
|
|
443
|
-
const key = scope || 'global';
|
|
444
|
-
const now = Date.now();
|
|
445
|
-
let state = _grepSweepState.get(key);
|
|
446
|
-
if (!state || now - state.lastTs > 10 * 60_000) {
|
|
447
|
-
state = { count: 0, unique: new Set(), lastTs: 0, nudged: false };
|
|
448
|
-
_grepSweepState.set(key, state);
|
|
449
|
-
}
|
|
450
|
-
state.count += 1;
|
|
451
|
-
state.unique.add(_grepSweepKey(args));
|
|
452
|
-
state.lastTs = now;
|
|
453
|
-
if (_grepSweepState.size > 256) {
|
|
454
|
-
const oldest = [..._grepSweepState.entries()].sort((a, b) => (a[1].lastTs || 0) - (b[1].lastTs || 0)).slice(0, 32);
|
|
455
|
-
for (const [oldKey] of oldest) _grepSweepState.delete(oldKey);
|
|
456
|
-
}
|
|
457
|
-
if (state.nudged || state.count < _GREP_SWEEP_MIN_CALLS || state.unique.size < _GREP_SWEEP_MIN_UNIQUE) return result;
|
|
458
|
-
state.nudged = true;
|
|
459
|
-
return `${result}\n\n[grep_sweep_hint] Many distinct grep searches were used in this session. Stop rewording search terms; synthesize from existing anchors or switch tools once (code_graph/find/read known regions).`;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
413
|
export async function executeBuiltinTool(name, args, cwd, options = {}) {
|
|
463
414
|
if (options.abortSignal && !options.signal) {
|
|
464
415
|
options = { ...options, signal: options.abortSignal };
|
|
@@ -521,8 +472,7 @@ export async function executeBuiltinTool(name, args, cwd, options = {}) {
|
|
|
521
472
|
return formatUnknownBuiltinToolMessage(name, args);
|
|
522
473
|
}
|
|
523
474
|
})();
|
|
524
|
-
|
|
525
|
-
return capToolOutput(_appendClampNotices(args, _withNudge), options);
|
|
475
|
+
return capToolOutput(_appendClampNotices(args, _toolResult), options);
|
|
526
476
|
}
|
|
527
477
|
|
|
528
478
|
// Surface arg-guard clamp notices (args._clampNotices, see pushClampNotice in
|
|
@@ -3,13 +3,13 @@ export const CODE_GRAPH_TOOL_DEFS = [
|
|
|
3
3
|
name: 'code_graph',
|
|
4
4
|
title: 'Code Graph',
|
|
5
5
|
annotations: { title: 'Code Graph', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: false, compressibleLossless: true },
|
|
6
|
-
description: 'Repo
|
|
6
|
+
description: 'Repo code structure/flow over verified source files only. IDs→graph; literal/zero→grep. Exact identifiers: find_symbol/references/callers/callees; keywords: symbol_search/search. Unsupported target arrays are omitted, never silently mixed. Batch symbols[]/files[] by mode.',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
|
-
mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'symbol_search', 'search', 'references', 'callers', 'callees'], description: '
|
|
11
|
-
files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: '
|
|
12
|
-
symbols: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Exact identifiers
|
|
10
|
+
mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'symbol_search', 'search', 'references', 'callers', 'callees'], description: 'File modes={overview,imports,dependents,related,impact}; symbols with files→files[] file outline; symbol modes={find_symbol,symbol_search,search,references,callers,callees}; fileless symbols→symbol_search keywords.' },
|
|
11
|
+
files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Source file path(s); supported targets only.' },
|
|
12
|
+
symbols: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Exact identifiers (find_symbol/references/callers/callees) or keywords (symbol_search/search); multiple exact symbols use one symbols[] call.' },
|
|
13
13
|
body: { type: 'boolean', description: 'Include body.' },
|
|
14
14
|
limit: { type: 'number', minimum: 1, description: 'Max results.' },
|
|
15
15
|
depth: { type: 'number', minimum: 1, maximum: 5, description: 'Caller depth.' },
|
|
@@ -363,7 +363,7 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
363
363
|
throw new Error('apply_patch: "patch" is required (unified diff or V4A patch string)');
|
|
364
364
|
}
|
|
365
365
|
if (isCompactedPlaceholderPatch(patchStr)) {
|
|
366
|
-
throw new Error('patch body is a compacted-history placeholder ([mixdog compacted …]), not real patch content. Re-read the target file
|
|
366
|
+
throw new Error('patch body is a compacted-history placeholder ([mixdog compacted …]), not real patch content and cannot be executed. Re-read the current target file contents now, then construct and submit a fresh full patch from those contents. Do not reuse the marker, omit the patch argument, or reconstruct the old patch from history.');
|
|
367
367
|
}
|
|
368
368
|
const patchByteLen = Buffer.byteLength(patchStr, 'utf8');
|
|
369
369
|
if (patchByteLen > APPLY_PATCH_MAX_BYTES) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.7.19",
|
|
3
|
+
"_comment": "Rewritten by .github/workflows/patch-release.yml on each tagged release. assets maps platformKey (process.platform-process.arch, e.g. win32-x64, linux-x64, darwin-arm64) to { url, sha256 } of the mixdog-patch binary on the GitHub release. A local cargo build under native/mixdog-patch/target/release always takes precedence; otherwise the binary is fetched per this manifest into the data dir (apply is native-only — no JS apply engine).",
|
|
4
|
+
"assets": {
|
|
5
|
+
"darwin-arm64": {
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.19/mixdog-patch-darwin-arm64",
|
|
7
|
+
"sha256": "c808fcfb69d2817fd7678dd364d2e1dd68f6ef2d9807f618bdf110c03ca00ccb"
|
|
8
|
+
},
|
|
9
|
+
"darwin-x64": {
|
|
10
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.19/mixdog-patch-darwin-x64",
|
|
11
|
+
"sha256": "7ed0eeff84796559ca865fc93e7a2a94f2c3fa00126f919064bb8273656fcd85"
|
|
12
|
+
},
|
|
13
|
+
"linux-arm64": {
|
|
14
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.19/mixdog-patch-linux-arm64",
|
|
15
|
+
"sha256": "3cad9c9542f2d97732953a102ef72daf4c79be5361560698d5fd2c5f0c18eda7"
|
|
16
|
+
},
|
|
17
|
+
"linux-x64": {
|
|
18
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.19/mixdog-patch-linux-x64",
|
|
19
|
+
"sha256": "cb0e30346f437811dff19238e1854bd37d97a78f35b13e0b704ebeaa9499ae87"
|
|
20
|
+
},
|
|
21
|
+
"win32-x64": {
|
|
22
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/patch-v0.7.19/mixdog-patch-win32-x64.exe",
|
|
23
|
+
"sha256": "4bf61a99f63e446570d9d2a9c9ef38475d0d70c0a2b632a5f44d4fe9b684b968"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -39,6 +39,29 @@ function trimOrNull(v) {
|
|
|
39
39
|
return s === '' ? null : s
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export function normalizeCoreInput(input = {}, options = {}) {
|
|
43
|
+
const summary = trimOrNull(input.summary ?? input.content)
|
|
44
|
+
const element = trimOrNull(input.element) ?? (summary ? summary.slice(0, CORE_ELEMENT_MAX) : null)
|
|
45
|
+
const suppliedCategory = trimOrNull(input.category)
|
|
46
|
+
const category = (suppliedCategory ?? 'fact').toLowerCase()
|
|
47
|
+
const errors = []
|
|
48
|
+
|
|
49
|
+
if (options.requireElement && !element) errors.push('element required')
|
|
50
|
+
if (options.requireSummary && !summary) errors.push('summary required')
|
|
51
|
+
if (options.requireCategory && !suppliedCategory) errors.push('category required')
|
|
52
|
+
if (element && element.length > CORE_ELEMENT_MAX) {
|
|
53
|
+
errors.push(`element too long (${element.length}/${CORE_ELEMENT_MAX} chars, remove ${element.length - CORE_ELEMENT_MAX})`)
|
|
54
|
+
}
|
|
55
|
+
if (summary && summary.length > CORE_SUMMARY_MAX) {
|
|
56
|
+
errors.push(`summary too long (${summary.length}/${CORE_SUMMARY_MAX} chars, remove ${summary.length - CORE_SUMMARY_MAX})`)
|
|
57
|
+
}
|
|
58
|
+
if (suppliedCategory && !VALID_CAT.has(category)) {
|
|
59
|
+
errors.push(`invalid category "${category}". Valid: ${[...VALID_CAT].join(', ')}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { element, summary, category, suppliedCategory, errors }
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
function _getDb(dataDir) {
|
|
43
66
|
if (!dataDir) throw new Error('core-memory: dataDir required')
|
|
44
67
|
const db = getDatabase(dataDir)
|
|
@@ -206,21 +229,14 @@ export async function listCore(dataDir, projectId = null) {
|
|
|
206
229
|
return r.rows
|
|
207
230
|
}
|
|
208
231
|
|
|
209
|
-
export async function addCore(dataDir,
|
|
232
|
+
export async function addCore(dataDir, input, projectId) {
|
|
210
233
|
if (projectId === undefined) throw new Error('addCore: projectId required — pass null for COMMON pool, or slug string for scoped pool')
|
|
211
|
-
const el =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (sm.length > CORE_SUMMARY_MAX) {
|
|
218
|
-
throw new Error(`core summary too long (${sm.length}/${CORE_SUMMARY_MAX} chars, remove ${sm.length - CORE_SUMMARY_MAX}) — 1 fact in 1-2 sentences; move procedures/multi-step/code to recap or docs.`)
|
|
219
|
-
}
|
|
220
|
-
const cat = (trimOrNull(category) ?? 'fact').toLowerCase()
|
|
221
|
-
if (!VALID_CAT.has(cat)) {
|
|
222
|
-
throw new Error(`invalid category "${cat}". Valid: ${[...VALID_CAT].join(', ')}`)
|
|
223
|
-
}
|
|
234
|
+
const { element: el, summary: sm, category: cat, errors } = normalizeCoreInput(input, {
|
|
235
|
+
requireElement: true,
|
|
236
|
+
requireSummary: true,
|
|
237
|
+
requireCategory: true,
|
|
238
|
+
})
|
|
239
|
+
if (errors.length) throw new Error(errors.join('; '))
|
|
224
240
|
const db = _getDb(dataDir)
|
|
225
241
|
const now = Date.now()
|
|
226
242
|
await _backfillNullEmbeddings(db)
|
|
@@ -299,19 +315,23 @@ export async function editCore(dataDir, id, patch) {
|
|
|
299
315
|
const db = _getDb(dataDir)
|
|
300
316
|
const cur = (await db.query(`SELECT * FROM core_entries WHERE id = $1`, [numId])).rows[0]
|
|
301
317
|
if (!cur) throw new Error(`no entry with id=${numId}`)
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const newCategory =
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
318
|
+
const incoming = normalizeCoreInput(patch)
|
|
319
|
+
const newElement = incoming.element ?? cur.element
|
|
320
|
+
const newSummary = incoming.summary ?? cur.summary
|
|
321
|
+
const newCategory = incoming.suppliedCategory ? incoming.category : cur.category
|
|
322
|
+
const { errors } = normalizeCoreInput({
|
|
323
|
+
element: newElement,
|
|
324
|
+
summary: newSummary,
|
|
325
|
+
category: newCategory,
|
|
326
|
+
}, {
|
|
327
|
+
requireElement: true,
|
|
328
|
+
requireSummary: true,
|
|
329
|
+
requireCategory: true,
|
|
330
|
+
})
|
|
331
|
+
if (errors.length) throw new Error(errors.join('; '))
|
|
309
332
|
if (newElement === cur.element && newSummary === cur.summary && newCategory === cur.category) {
|
|
310
333
|
throw new Error('no change')
|
|
311
334
|
}
|
|
312
|
-
if (newSummary && newSummary.length > CORE_SUMMARY_MAX) {
|
|
313
|
-
throw new Error(`core summary too long (${newSummary.length}/${CORE_SUMMARY_MAX} chars, remove ${newSummary.length - CORE_SUMMARY_MAX}) — 1 fact in 1-2 sentences; move procedures/multi-step/code to recap or docs.`)
|
|
314
|
-
}
|
|
315
335
|
const now = Date.now()
|
|
316
336
|
const textChanged = newElement !== cur.element || newSummary !== cur.summary
|
|
317
337
|
if (!textChanged) {
|
|
@@ -45,7 +45,6 @@ export function createHttpRouter({
|
|
|
45
45
|
registerClient,
|
|
46
46
|
deregisterClient,
|
|
47
47
|
getDraining,
|
|
48
|
-
getCycle1CallLlm,
|
|
49
48
|
getTraceDb,
|
|
50
49
|
setTraceDb,
|
|
51
50
|
ingestTranscriptFile,
|
|
@@ -425,198 +424,6 @@ export function createHttpRouter({
|
|
|
425
424
|
return
|
|
426
425
|
}
|
|
427
426
|
|
|
428
|
-
// DEV-ONLY cycle1 chunking bench. Gated by env MIXDOG_DEV_BENCH=1 so
|
|
429
|
-
// production is untouched (route returns 404 when unset). Mirrors cycle1's
|
|
430
|
-
// exact fetch query + per-session windowing, then runs each window through
|
|
431
|
-
// buildCycle1ChunkPrompt + callAgentDispatch + parseCycle1LineFormat. STRICT
|
|
432
|
-
// read-only — no UPDATE, no transaction, no commit.
|
|
433
|
-
if (req.method === 'POST' && req.url === '/dev/cycle1-bench') {
|
|
434
|
-
const db = getDb()
|
|
435
|
-
// Gate: env MIXDOG_DEV_BENCH=1 OR a runtime flag file, so it can be
|
|
436
|
-
// toggled without restarting the host agent (env only reaches the worker
|
|
437
|
-
// on a full CC restart, not via dev-sync full-restart).
|
|
438
|
-
const _devBenchOn = process.env.MIXDOG_DEV_BENCH === '1'
|
|
439
|
-
|| (DATA_DIR && fs.existsSync(path.join(DATA_DIR, '.dev-bench-enabled')))
|
|
440
|
-
if (!_devBenchOn) {
|
|
441
|
-
sendJson(res, { error: 'not found' }, 404)
|
|
442
|
-
return
|
|
443
|
-
}
|
|
444
|
-
if (!isLocalOrigin(req)) {
|
|
445
|
-
sendJson(res, { ok: false, error: 'forbidden: cross-origin' }, 403)
|
|
446
|
-
return
|
|
447
|
-
}
|
|
448
|
-
try {
|
|
449
|
-
const body = await readBody(req)
|
|
450
|
-
const sets = Math.max(1, Number(body?.sets ?? 5))
|
|
451
|
-
const repeat = Math.max(1, Number(body?.repeat ?? 1))
|
|
452
|
-
// Optional variant matrix. Each variant: {name, rules}. rules=null → default prompt.
|
|
453
|
-
const rawVariants = Array.isArray(body?.variants) ? body.variants : null
|
|
454
|
-
const variants = rawVariants && rawVariants.length > 0
|
|
455
|
-
? rawVariants.map((v, i) => ({
|
|
456
|
-
name: typeof v?.name === 'string' && v.name ? v.name : `variant-${i + 1}`,
|
|
457
|
-
rules: Array.isArray(v?.rules) ? v.rules : null,
|
|
458
|
-
}))
|
|
459
|
-
: null
|
|
460
|
-
|
|
461
|
-
// Lazy-load LLM + chunking helpers so production boot pays nothing.
|
|
462
|
-
// Use the same in-process agent dispatch adapter as real cycle1 — the legacy
|
|
463
|
-
// agent-ipc callAgentDispatch() path is dead in the detached standalone
|
|
464
|
-
// memory daemon (no connected IPC), so the dev bench must mirror prod.
|
|
465
|
-
const [{ buildCycle1ChunkPrompt, parseCycle1LineFormat }, { resolveMaintenancePreset }] = await Promise.all([
|
|
466
|
-
import('./memory-cycle1.mjs'),
|
|
467
|
-
import('../../shared/llm/index.mjs'),
|
|
468
|
-
])
|
|
469
|
-
const benchCallLlm = getCycle1CallLlm()
|
|
470
|
-
|
|
471
|
-
const CYCLE1_MIN_BATCH = 3
|
|
472
|
-
const CYCLE1_SESSION_CAP = 10
|
|
473
|
-
const BATCH_SIZE = 100
|
|
474
|
-
const TIMEOUT_MS = 180_000
|
|
475
|
-
const fetchLimit = CYCLE1_SESSION_CAP * BATCH_SIZE
|
|
476
|
-
|
|
477
|
-
const fetchResult = await db.query(
|
|
478
|
-
`SELECT id, ts, role, content, session_id, source_ref, project_id
|
|
479
|
-
FROM entries
|
|
480
|
-
WHERE chunk_root IS NULL AND session_id IS NOT NULL
|
|
481
|
-
ORDER BY ts DESC, id DESC
|
|
482
|
-
LIMIT $1`,
|
|
483
|
-
[fetchLimit],
|
|
484
|
-
)
|
|
485
|
-
const rowsDesc = fetchResult.rows
|
|
486
|
-
|
|
487
|
-
if (rowsDesc.length < CYCLE1_MIN_BATCH) {
|
|
488
|
-
sendJson(res, {
|
|
489
|
-
ok: true,
|
|
490
|
-
sets, repeat,
|
|
491
|
-
windowsAvailable: 0,
|
|
492
|
-
note: `not enough pending rows (need >= ${CYCLE1_MIN_BATCH}, got ${rowsDesc.length})`,
|
|
493
|
-
results: [],
|
|
494
|
-
})
|
|
495
|
-
return
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Partition by session_id — same as memory-cycle1.mjs _runCycle1Impl L207-233.
|
|
499
|
-
const sessionMap = new Map()
|
|
500
|
-
for (const row of rowsDesc.slice().reverse()) {
|
|
501
|
-
const sid = row.session_id
|
|
502
|
-
if (!sessionMap.has(sid)) sessionMap.set(sid, [])
|
|
503
|
-
sessionMap.get(sid).push(row)
|
|
504
|
-
}
|
|
505
|
-
const windows = []
|
|
506
|
-
for (const [sid, sessionRows] of sessionMap) {
|
|
507
|
-
if (sessionRows.length < CYCLE1_MIN_BATCH) continue
|
|
508
|
-
const windowCount = Math.max(1, Math.ceil(sessionRows.length / BATCH_SIZE))
|
|
509
|
-
const baseSize = Math.floor(sessionRows.length / windowCount)
|
|
510
|
-
const remainder = sessionRows.length % windowCount
|
|
511
|
-
let _offset = 0
|
|
512
|
-
for (let i = 0; i < windowCount; i++) {
|
|
513
|
-
const size = baseSize + (i < remainder ? 1 : 0)
|
|
514
|
-
windows.push({ sid, rows: sessionRows.slice(_offset, _offset + size) })
|
|
515
|
-
_offset += size
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
const chosen = windows.slice(0, sets)
|
|
519
|
-
|
|
520
|
-
const preset = resolveMaintenancePreset('memory')
|
|
521
|
-
|
|
522
|
-
function summariseChunks(chunks, totalEntries) {
|
|
523
|
-
const usedIdx = new Set()
|
|
524
|
-
for (const c of chunks) for (const i of (c._idxList || [])) usedIdx.add(i)
|
|
525
|
-
const omitted = []
|
|
526
|
-
for (let i = 1; i <= totalEntries; i++) if (!usedIdx.has(i)) omitted.push(i)
|
|
527
|
-
return { covered: usedIdx.size, omitted }
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// When variants are absent, fall back to a single implicit baseline so the
|
|
531
|
-
// pre-variant call shape (single rows × repeat) keeps producing the same
|
|
532
|
-
// {runs:[…]} payload the trigger already knows how to print.
|
|
533
|
-
const variantList = variants ?? [{ name: 'baseline', rules: null }]
|
|
534
|
-
|
|
535
|
-
async function runOnce(rows, customRules) {
|
|
536
|
-
const userMessage = buildCycle1ChunkPrompt(rows, customRules)
|
|
537
|
-
const t0 = Date.now()
|
|
538
|
-
let raw, error
|
|
539
|
-
try {
|
|
540
|
-
raw = await benchCallLlm({
|
|
541
|
-
preset,
|
|
542
|
-
timeout: TIMEOUT_MS,
|
|
543
|
-
}, userMessage)
|
|
544
|
-
} catch (e) {
|
|
545
|
-
error = e?.message ?? String(e)
|
|
546
|
-
}
|
|
547
|
-
const llmMs = Date.now() - t0
|
|
548
|
-
if (error) return { ok: false, llmMs, error }
|
|
549
|
-
const parsed = parseCycle1LineFormat(raw)
|
|
550
|
-
const chunks = Array.isArray(parsed?.chunks) ? parsed.chunks : []
|
|
551
|
-
const { covered, omitted } = summariseChunks(chunks, rows.length)
|
|
552
|
-
const ratio = chunks.length > 0
|
|
553
|
-
? parseFloat((rows.length / chunks.length).toFixed(2))
|
|
554
|
-
: null
|
|
555
|
-
return {
|
|
556
|
-
ok: true,
|
|
557
|
-
llmMs,
|
|
558
|
-
entries: rows.length,
|
|
559
|
-
chunks: chunks.length,
|
|
560
|
-
ratio,
|
|
561
|
-
covered,
|
|
562
|
-
omitted,
|
|
563
|
-
chunkList: chunks.map(c => ({
|
|
564
|
-
idx: c._idxList,
|
|
565
|
-
element: c.element,
|
|
566
|
-
category: c.category,
|
|
567
|
-
summary: c.summary,
|
|
568
|
-
})),
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const results = []
|
|
573
|
-
for (let s = 0; s < chosen.length; s++) {
|
|
574
|
-
const { sid, rows } = chosen[s]
|
|
575
|
-
const sidShort = String(sid).slice(0, 8)
|
|
576
|
-
if (variants) {
|
|
577
|
-
// Variant mode: same rows, one run per variant per repeat.
|
|
578
|
-
const variantResults = []
|
|
579
|
-
for (const v of variantList) {
|
|
580
|
-
const runs = []
|
|
581
|
-
for (let r = 0; r < repeat; r++) {
|
|
582
|
-
const run = await runOnce(rows, v.rules)
|
|
583
|
-
runs.push({ repIdx: r + 1, ...run })
|
|
584
|
-
}
|
|
585
|
-
variantResults.push({ name: v.name, runs })
|
|
586
|
-
}
|
|
587
|
-
results.push({
|
|
588
|
-
setIdx: s + 1,
|
|
589
|
-
sessionIdShort: sidShort,
|
|
590
|
-
entries: rows.length,
|
|
591
|
-
variants: variantResults,
|
|
592
|
-
})
|
|
593
|
-
} else {
|
|
594
|
-
// Legacy single-baseline payload shape.
|
|
595
|
-
const runs = []
|
|
596
|
-
for (let r = 0; r < repeat; r++) {
|
|
597
|
-
const run = await runOnce(rows, null)
|
|
598
|
-
runs.push({ repIdx: r + 1, ...run })
|
|
599
|
-
}
|
|
600
|
-
results.push({
|
|
601
|
-
setIdx: s + 1,
|
|
602
|
-
sessionIdShort: sidShort,
|
|
603
|
-
entries: rows.length,
|
|
604
|
-
runs,
|
|
605
|
-
})
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
sendJson(res, {
|
|
609
|
-
ok: true,
|
|
610
|
-
sets, repeat,
|
|
611
|
-
windowsAvailable: windows.length,
|
|
612
|
-
variants: variants ? variantList.map(v => v.name) : null,
|
|
613
|
-
results,
|
|
614
|
-
})
|
|
615
|
-
} catch (e) {
|
|
616
|
-
sendError(res, e?.message || String(e))
|
|
617
|
-
}
|
|
618
|
-
return
|
|
619
|
-
}
|
|
620
427
|
|
|
621
428
|
if (req.method === 'POST' && req.url === '/api/tool') {
|
|
622
429
|
if (!isLocalOrigin(req)) {
|
|
@@ -33,8 +33,9 @@ import {
|
|
|
33
33
|
promoteCoreCandidate,
|
|
34
34
|
dismissCoreCandidate,
|
|
35
35
|
CORE_SUMMARY_MAX,
|
|
36
|
+
normalizeCoreInput,
|
|
36
37
|
} from './core-memory-store.mjs'
|
|
37
|
-
import { resolveProjectScope } from './project-id-resolver.mjs'
|
|
38
|
+
import { resolveProjectId, resolveProjectScope } from './project-id-resolver.mjs'
|
|
38
39
|
import { resolvePluginData } from '../../shared/plugin-paths.mjs'
|
|
39
40
|
import { getMetaValue, isBootstrapComplete } from './memory.mjs'
|
|
40
41
|
|
|
@@ -57,6 +58,7 @@ export function createMemoryActionHandlers({
|
|
|
57
58
|
getCycle3CallLlm,
|
|
58
59
|
ingestTranscriptFile,
|
|
59
60
|
cwdFromTranscriptPath,
|
|
61
|
+
editCoreImpl = editCore,
|
|
60
62
|
}) {
|
|
61
63
|
const DATA_DIR = dataDir
|
|
62
64
|
|
|
@@ -643,16 +645,43 @@ export function createMemoryActionHandlers({
|
|
|
643
645
|
}
|
|
644
646
|
// Local trim helper — the manage-block trimOrNull at :1807 is scoped to
|
|
645
647
|
// that branch and unreachable from here.
|
|
646
|
-
//
|
|
648
|
+
// Only the explicit string "common" selects the COMMON pool. Core add
|
|
649
|
+
// validates missing, null, and blank project_id below; edits use the
|
|
650
|
+
// target entry's stored pool.
|
|
647
651
|
const hasProjectIdKey = Object.prototype.hasOwnProperty.call(args, 'project_id')
|
|
652
|
+
const projectIdText = typeof args.project_id === 'string' ? args.project_id.trim() : ''
|
|
648
653
|
const projectId = (() => {
|
|
649
|
-
if (
|
|
650
|
-
|
|
651
|
-
if (s === '' || s.toLowerCase() === 'common') return null
|
|
652
|
-
if (s === '*') return '*'
|
|
653
|
-
return s
|
|
654
|
+
if (projectIdText.toLowerCase() === 'common') return null
|
|
655
|
+
return projectIdText || null
|
|
654
656
|
})()
|
|
655
657
|
try {
|
|
658
|
+
if (op === 'add' || op === 'edit') {
|
|
659
|
+
const normalized = normalizeCoreInput(args, {
|
|
660
|
+
requireElement: true,
|
|
661
|
+
requireSummary: true,
|
|
662
|
+
requireCategory: true,
|
|
663
|
+
})
|
|
664
|
+
const errors = [...normalized.errors]
|
|
665
|
+
if (op === 'add') {
|
|
666
|
+
if (!hasProjectIdKey || !projectIdText) {
|
|
667
|
+
const inferredProjectId = typeof args.cwd === 'string' && args.cwd
|
|
668
|
+
? resolveProjectId(args.cwd)
|
|
669
|
+
: null
|
|
670
|
+
errors.unshift(
|
|
671
|
+
`project_id required — pass "common" for COMMON pool, or project slug like "owner/repo" for scoped pool` +
|
|
672
|
+
(typeof inferredProjectId === 'string' && /^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)?$/.test(inferredProjectId)
|
|
673
|
+
? ` (cwd suggests "${inferredProjectId}")`
|
|
674
|
+
: ''),
|
|
675
|
+
)
|
|
676
|
+
} else if (projectId === '*') {
|
|
677
|
+
errors.unshift('project_id "*" only valid for op="list"')
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (errors.length) {
|
|
681
|
+
return { text: `core ${op}: ${errors.join('; ')}`, isError: true }
|
|
682
|
+
}
|
|
683
|
+
args = { ...args, element: normalized.element, summary: normalized.summary, category: normalized.category }
|
|
684
|
+
}
|
|
656
685
|
if (projectId === '*' && op !== 'list') {
|
|
657
686
|
return { text: `core ${op}: project_id "*" only valid for op="list"`, isError: true }
|
|
658
687
|
}
|
|
@@ -681,14 +710,11 @@ export function createMemoryActionHandlers({
|
|
|
681
710
|
return { text: lines.join('\n') }
|
|
682
711
|
}
|
|
683
712
|
if (op === 'add') {
|
|
684
|
-
if (!hasProjectIdKey) {
|
|
685
|
-
return { text: 'core add: project_id required — pass "common" for COMMON pool, or project slug like "owner/repo" for scoped pool', isError: true }
|
|
686
|
-
}
|
|
687
713
|
const entry = await addCore(coreDataDir, args, projectId)
|
|
688
714
|
return { text: `core added (id=${entry.id}): [${entry.category}] ${entry.element} — ${entry.summary.slice(0, 200)}` }
|
|
689
715
|
}
|
|
690
716
|
if (op === 'edit') {
|
|
691
|
-
const entry = await
|
|
717
|
+
const entry = await editCoreImpl(coreDataDir, args.id, args)
|
|
692
718
|
return { text: `core edited (id=${entry.id}): [${entry.category}] ${entry.element} — ${entry.summary.slice(0, 200)}` }
|
|
693
719
|
}
|
|
694
720
|
if (op === 'delete') {
|
|
@@ -8,20 +8,20 @@ export const TOOL_DEFS = [
|
|
|
8
8
|
name: 'memory',
|
|
9
9
|
title: 'Memory Cycle',
|
|
10
10
|
annotations: { title: 'Memory Cycle', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
11
|
-
description: 'Core-memory mutation and status
|
|
11
|
+
description: 'Core-memory mutation and status; use recall for retrieval. Store durable rules/preferences/facts, one short clause each — never transient task state. add requires project_id+category+summary; edit works by id alone.',
|
|
12
12
|
inputSchema: {
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
|
15
15
|
action: { type: 'string', enum: ['core','status'], description: 'Operation.' },
|
|
16
16
|
op: { type: 'string', enum: ['add','edit','delete','list','candidates','promote','dismiss'], description: 'Mutation op. candidates/promote/dismiss drive core-memory proposal approval.' },
|
|
17
17
|
id: { type: 'number', description: 'Exact memory id.' },
|
|
18
|
-
element: { type: 'string', maxLength: 40, description: 'Memory key/title. Max 40 chars.' },
|
|
19
|
-
summary: { type: 'string', maxLength: 100, description: 'Memory content:
|
|
18
|
+
element: { type: 'string', maxLength: 40, description: 'Memory key/title. Defaults to the first 40 chars of summary. Max 40 chars.' },
|
|
19
|
+
summary: { type: 'string', maxLength: 100, description: 'Memory content: one short fact, max 100 chars.' },
|
|
20
20
|
category: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'], description: 'Category.' },
|
|
21
21
|
status: { type: 'string', enum: ['pending','active','archived'], description: 'Lifecycle status.' },
|
|
22
22
|
limit: { type: 'number', description: 'Max rows/items.' },
|
|
23
23
|
confirm: { type: 'string', description: 'Exact confirmation phrase for destructive actions.' },
|
|
24
|
-
project_id: { type: 'string', description: 'Core pool: common
|
|
24
|
+
project_id: { type: 'string', description: 'Core pool: explicit common or slug. Required for core add only (edit uses the id\'s stored pool); there is no default pool.' },
|
|
25
25
|
},
|
|
26
26
|
additionalProperties: false,
|
|
27
27
|
required: ['action'],
|
|
@@ -31,7 +31,7 @@ export const TOOL_DEFS = [
|
|
|
31
31
|
name: 'recall',
|
|
32
32
|
title: 'Recall',
|
|
33
33
|
annotations: { title: 'Recall', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
34
|
-
description: 'Retrieve stored memory/session history
|
|
34
|
+
description: 'Retrieve stored memory/session history for prior-work context (resumes, earlier decisions); not a reflexive pre-step. Query is topic/semantic, not regex; period selects the time window; id for exact follow-up.',
|
|
35
35
|
inputSchema: {
|
|
36
36
|
type: 'object',
|
|
37
37
|
properties: {
|
|
@@ -44,7 +44,6 @@ export const TOOL_DEFS = [
|
|
|
44
44
|
category: { anyOf: [{ type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, { type: 'array', items: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, minItems: 1 }], description: 'Category filter.' },
|
|
45
45
|
includeArchived: { type: 'boolean', description: 'Include archived entries.' },
|
|
46
46
|
sessionId: { type: 'string', description: 'Scoped session id.' },
|
|
47
|
-
session_id: { type: 'string', description: 'Alias for sessionId.' },
|
|
48
47
|
includeMembers: { type: 'boolean', description: 'Include chunk members in output; does not widen the search pool.' },
|
|
49
48
|
includeRaw: { type: 'boolean', description: 'Include unchunked raw/episode rows.' },
|
|
50
49
|
sessionOnly: { type: 'boolean', description: 'Search this session only.' },
|
|
@@ -14,6 +14,17 @@ import {
|
|
|
14
14
|
loadLatestMixdogConfigFromBackup,
|
|
15
15
|
hasUserDataInitMarker,
|
|
16
16
|
} from './user-data-guard.mjs'
|
|
17
|
+
import {
|
|
18
|
+
AGENT_PROVIDER_ENV,
|
|
19
|
+
AGENT_PROVIDER_ENV_ALIASES,
|
|
20
|
+
getAgentApiKey,
|
|
21
|
+
} from './provider-api-key.mjs'
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
AGENT_PROVIDER_ENV,
|
|
25
|
+
AGENT_PROVIDER_ENV_ALIASES,
|
|
26
|
+
getAgentApiKey,
|
|
27
|
+
}
|
|
17
28
|
|
|
18
29
|
const _require = createRequire(import.meta.url)
|
|
19
30
|
const { getSecret: _getSecret, setSecret: _setSecret, deleteSecret: _deleteSecret, hasSecret: _hasSecret } = _require('../../lib/keychain-cjs.cjs')
|
|
@@ -453,40 +464,6 @@ export function getWebhookAuthtoken() {
|
|
|
453
464
|
return _readSecret(SECRET_ACCOUNTS.webhookAuth)
|
|
454
465
|
}
|
|
455
466
|
|
|
456
|
-
// Standard provider env names take precedence so existing OPENAI_API_KEY-style
|
|
457
|
-
// exports keep working, then MIXDOG_AGENT_<P>_APIKEY, then the OS keychain.
|
|
458
|
-
// SSOT for agent API-key providers: setup-server.mjs and config-merge.mjs import
|
|
459
|
-
// this so UI key-presence detection uses the exact same predicate the runtime
|
|
460
|
-
// loads from. Add a provider here and every path picks it up.
|
|
461
|
-
export const AGENT_PROVIDER_ENV = Object.freeze({
|
|
462
|
-
openai: 'OPENAI_API_KEY', anthropic: 'ANTHROPIC_API_KEY', gemini: 'GEMINI_API_KEY',
|
|
463
|
-
deepseek: 'DEEPSEEK_API_KEY', xai: 'XAI_API_KEY', 'opencode-go': 'OPENCODE_API_KEY',
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
// Last-resort env aliases honored AFTER the standard env / MIXDOG_AGENT_* /
|
|
467
|
-
// keychain sources. GROK_API_KEY is the established xAI alias elsewhere in the
|
|
468
|
-
// repo (search discovery, xai-api/grok-oauth backends), so honoring it here
|
|
469
|
-
// keeps provider discovery and dispatch resolving the same credential.
|
|
470
|
-
export const AGENT_PROVIDER_ENV_ALIASES = Object.freeze({
|
|
471
|
-
xai: ['GROK_API_KEY'],
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Returns the API key for an agent provider.
|
|
476
|
-
* Priority: <PROVIDER>_API_KEY env -> MIXDOG_AGENT_<PROVIDER>_APIKEY -> keychain('agent.<provider>.apiKey') -> alias env (e.g. GROK_API_KEY for xai) -> null.
|
|
477
|
-
* Never reads mixdog-config.json — provider keys are keychain-only.
|
|
478
|
-
*/
|
|
479
|
-
export function getAgentApiKey(provider) {
|
|
480
|
-
const std = AGENT_PROVIDER_ENV[provider]
|
|
481
|
-
if (std && process.env[std]) return process.env[std]
|
|
482
|
-
const fromStore = _readSecret(SECRET_ACCOUNTS.agentApiKey(provider))
|
|
483
|
-
if (fromStore) return fromStore
|
|
484
|
-
for (const alias of AGENT_PROVIDER_ENV_ALIASES[provider] || []) {
|
|
485
|
-
if (process.env[alias]) return process.env[alias]
|
|
486
|
-
}
|
|
487
|
-
return null
|
|
488
|
-
}
|
|
489
|
-
|
|
490
467
|
export function getOpenAIUsageSessionKey() {
|
|
491
468
|
return process.env.OPENAI_USAGE_SESSION_KEY
|
|
492
469
|
|| process.env.OPENAI_DASHBOARD_SESSION_KEY
|