mixdog 0.9.45 → 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/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 +23 -10
- 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 +77 -65
- 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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"configFileName": "mixdog-config.json",
|
|
4
|
+
"guardEnv": {
|
|
5
|
+
"MIXDOG_DISABLE_MCP": "1",
|
|
6
|
+
"MIXDOG_DISABLE_SKILLS": "1",
|
|
7
|
+
"MIXDOG_BOOT_CORE_MEMORY": "0",
|
|
8
|
+
"MIXDOG_DISABLE_CHANNEL_START": "1",
|
|
9
|
+
"MIXDOG_SKIP_USER_DATA_BACKUP": "1",
|
|
10
|
+
"MIXDOG_DISABLE_STAGED_INSTALL": "1",
|
|
11
|
+
"MIXDOG_DISABLE_STAGED_SWAP": "1"
|
|
12
|
+
},
|
|
13
|
+
"approvedExecutionEnv": [
|
|
14
|
+
"MIXDOG_AGENT_TRACE_PATH",
|
|
15
|
+
"MIXDOG_ANTHROPIC_OAUTH_REFRESH_DISABLED",
|
|
16
|
+
"MIXDOG_DRIVER_DEADLINE_MS",
|
|
17
|
+
"MIXDOG_DISABLE_PROVIDER_WARMUP",
|
|
18
|
+
"MIXDOG_DISABLE_MODEL_PREFETCH",
|
|
19
|
+
"MIXDOG_DISABLE_MODEL_CATALOG_WARMUP",
|
|
20
|
+
"MIXDOG_PROVIDER_FIRST_BYTE_TIMEOUT_MS",
|
|
21
|
+
"MIXDOG_STALL_FIRST_BYTE_ABORT_S"
|
|
22
|
+
],
|
|
23
|
+
"benchmarkRouteEnv": [
|
|
24
|
+
"MIXDOG_PROMPT",
|
|
25
|
+
"MIXDOG_PROVIDER",
|
|
26
|
+
"MIXDOG_MODEL",
|
|
27
|
+
"MIXDOG_EFFORT",
|
|
28
|
+
"MIXDOG_FAST",
|
|
29
|
+
"MIXDOG_WORKFLOW"
|
|
30
|
+
],
|
|
31
|
+
"oauthProviders": {
|
|
32
|
+
"anthropic-oauth": {
|
|
33
|
+
"credentialFile": "anthropic-oauth-credentials.json",
|
|
34
|
+
"credentialPathEnv": "ANTHROPIC_OAUTH_CREDENTIALS_PATH",
|
|
35
|
+
"modelCatalogFile": "anthropic-oauth-models.json"
|
|
36
|
+
},
|
|
37
|
+
"openai-oauth": {
|
|
38
|
+
"credentialFile": "openai-oauth.json",
|
|
39
|
+
"credentialPathEnv": "OPENAI_OAUTH_CREDENTIALS_PATH",
|
|
40
|
+
"modelCatalogFile": "openai-oauth-models.json"
|
|
41
|
+
},
|
|
42
|
+
"grok-oauth": {
|
|
43
|
+
"credentialFile": "grok-oauth.json",
|
|
44
|
+
"credentialPathEnv": "GROK_OAUTH_CREDENTIALS_PATH",
|
|
45
|
+
"modelCatalogFile": "grok-oauth-models.json"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"apiKeyProviders": {
|
|
49
|
+
"openai": "OPENAI_API_KEY",
|
|
50
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
51
|
+
"gemini": "GEMINI_API_KEY",
|
|
52
|
+
"deepseek": "DEEPSEEK_API_KEY",
|
|
53
|
+
"xai": "XAI_API_KEY",
|
|
54
|
+
"opencode-go": "OPENCODE_API_KEY"
|
|
55
|
+
},
|
|
56
|
+
"personalStateCounters": [
|
|
57
|
+
"behavioralStateFilesCopied",
|
|
58
|
+
"memoryFilesCopied",
|
|
59
|
+
"sessionFilesCopied",
|
|
60
|
+
"userProfileFilesCopied",
|
|
61
|
+
"pluginStateFilesCopied",
|
|
62
|
+
"channelStateFilesCopied",
|
|
63
|
+
"otherPersonalStateFilesCopied"
|
|
64
|
+
],
|
|
65
|
+
"disabledFeatures": [
|
|
66
|
+
"mcpSources",
|
|
67
|
+
"skills",
|
|
68
|
+
"bundledSkillSeeding",
|
|
69
|
+
"coreMemoryBoot",
|
|
70
|
+
"channelStart",
|
|
71
|
+
"plugins",
|
|
72
|
+
"userProfiles",
|
|
73
|
+
"priorSessions"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { homedir, tmpdir } from 'node:os';
|
|
12
|
+
import { join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { replaceProviderAuthBindings } from './provider-auth-binding.mjs';
|
|
15
|
+
import {
|
|
16
|
+
AGENT_PROVIDER_ENV_ALIASES,
|
|
17
|
+
getAgentApiKey,
|
|
18
|
+
} from './provider-api-key.mjs';
|
|
19
|
+
|
|
20
|
+
const contractPath = fileURLToPath(
|
|
21
|
+
new URL('./pristine-execution-contract.json', import.meta.url),
|
|
22
|
+
);
|
|
23
|
+
const patchManifestPath = fileURLToPath(
|
|
24
|
+
new URL('../agent/orchestrator/tools/patch-manifest.json', import.meta.url),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const PRISTINE_EXECUTION_CONTRACT = Object.freeze(
|
|
28
|
+
JSON.parse(readFileSync(contractPath, 'utf8')),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
32
|
+
|
|
33
|
+
function clean(value) {
|
|
34
|
+
return String(value ?? '').trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateExplicitPristineRoute({ provider, model, effort, fast } = {}) {
|
|
38
|
+
const selectedProvider = clean(provider);
|
|
39
|
+
const selectedModel = clean(model);
|
|
40
|
+
if (!selectedProvider || !selectedModel) {
|
|
41
|
+
return 'headless role commands require both --provider <name> and --model <name>; host route fallback is disabled';
|
|
42
|
+
}
|
|
43
|
+
const selectedEffort = clean(effort).toLowerCase();
|
|
44
|
+
if (selectedEffort && !EFFORTS.has(selectedEffort)) {
|
|
45
|
+
return `invalid --effort ${JSON.stringify(clean(effort))}; expected low, medium, high, xhigh, or max`;
|
|
46
|
+
}
|
|
47
|
+
if (fast !== undefined && typeof fast !== 'boolean') {
|
|
48
|
+
return '--fast must be an explicit boolean flag';
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildMinimalPristineConfig({ provider, model, effort, fast } = {}) {
|
|
54
|
+
const routeError = validateExplicitPristineRoute({ provider, model, effort, fast });
|
|
55
|
+
if (routeError) throw new Error(routeError);
|
|
56
|
+
const selectedProvider = clean(provider);
|
|
57
|
+
const selectedModel = clean(model);
|
|
58
|
+
const selectedEffort = clean(effort).toLowerCase();
|
|
59
|
+
const route = {
|
|
60
|
+
provider: selectedProvider,
|
|
61
|
+
model: selectedModel,
|
|
62
|
+
...(selectedEffort ? { effort: selectedEffort } : {}),
|
|
63
|
+
...(fast === true ? { fast: true } : {}),
|
|
64
|
+
};
|
|
65
|
+
const providerConfig = {
|
|
66
|
+
enabled: true,
|
|
67
|
+
...(selectedProvider === 'openai-oauth' ? { websocket: true } : {}),
|
|
68
|
+
};
|
|
69
|
+
const modelSettings = {};
|
|
70
|
+
if (selectedEffort || fast === true) {
|
|
71
|
+
modelSettings[`${selectedProvider}/${selectedModel}`] = {
|
|
72
|
+
...(selectedEffort ? { effort: selectedEffort } : {}),
|
|
73
|
+
...(fast === true ? { fast: true } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
agent: {
|
|
78
|
+
providers: { [selectedProvider]: providerConfig },
|
|
79
|
+
presets: [{
|
|
80
|
+
id: 'headless-explicit-route',
|
|
81
|
+
name: 'HEADLESS EXPLICIT ROUTE',
|
|
82
|
+
type: 'agent',
|
|
83
|
+
tools: 'full',
|
|
84
|
+
...route,
|
|
85
|
+
}],
|
|
86
|
+
default: 'headless-explicit-route',
|
|
87
|
+
workflow: { active: 'default' },
|
|
88
|
+
workflowRoutes: {},
|
|
89
|
+
agents: {},
|
|
90
|
+
modelSettings,
|
|
91
|
+
mcpServers: {},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function cloneJson(value) {
|
|
97
|
+
return JSON.parse(JSON.stringify(value));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createSelectedProviderPristineLoader({
|
|
101
|
+
config,
|
|
102
|
+
provider,
|
|
103
|
+
apiKey = null,
|
|
104
|
+
} = {}) {
|
|
105
|
+
const selectedProvider = clean(provider);
|
|
106
|
+
const runtimeConfig = cloneJson(config?.agent || {});
|
|
107
|
+
const selectedConfig = {
|
|
108
|
+
...(runtimeConfig.providers?.[selectedProvider] || {}),
|
|
109
|
+
enabled: true,
|
|
110
|
+
...(apiKey ? { apiKey } : {}),
|
|
111
|
+
};
|
|
112
|
+
runtimeConfig.providers = { [selectedProvider]: selectedConfig };
|
|
113
|
+
return () => cloneJson(runtimeConfig);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function hostDataDir(env) {
|
|
117
|
+
if (clean(env.MIXDOG_DATA_DIR)) return resolve(env.MIXDOG_DATA_DIR);
|
|
118
|
+
const home = clean(env.MIXDOG_HOME)
|
|
119
|
+
? resolve(env.MIXDOG_HOME)
|
|
120
|
+
: join(homedir(), '.mixdog');
|
|
121
|
+
return join(home, 'data');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function patchPlatformKey() {
|
|
125
|
+
const os = process.platform === 'win32' ? 'win32' : process.platform;
|
|
126
|
+
return `${os}-${process.arch}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function seedVerifiedPatchBinaryCache(
|
|
130
|
+
sourceDataDir,
|
|
131
|
+
dataDir,
|
|
132
|
+
{ manifestPath = patchManifestPath } = {},
|
|
133
|
+
) {
|
|
134
|
+
try {
|
|
135
|
+
const sourcePatchDir = join(sourceDataDir, 'patch-bin');
|
|
136
|
+
const manifestBytes = readFileSync(manifestPath);
|
|
137
|
+
const manifest = JSON.parse(manifestBytes.toString('utf8'));
|
|
138
|
+
const asset = manifest?.assets?.[patchPlatformKey()];
|
|
139
|
+
const version = String(manifest?.version || '');
|
|
140
|
+
const expectedSha256 = clean(asset?.sha256).toLowerCase();
|
|
141
|
+
if (!/^[A-Za-z0-9._-]+$/.test(version) || !/^[a-f0-9]{64}$/.test(expectedSha256)) return false;
|
|
142
|
+
|
|
143
|
+
const binaryName = `mixdog-patch-${version}${process.platform === 'win32' ? '.exe' : ''}`;
|
|
144
|
+
const sourceBinary = join(sourcePatchDir, binaryName);
|
|
145
|
+
if (!existsSync(sourceBinary)) return false;
|
|
146
|
+
const binaryBytes = readFileSync(sourceBinary);
|
|
147
|
+
const actualSha256 = createHash('sha256').update(binaryBytes).digest('hex');
|
|
148
|
+
if (actualSha256 !== expectedSha256) return false;
|
|
149
|
+
|
|
150
|
+
const targetPatchDir = join(dataDir, 'patch-bin');
|
|
151
|
+
mkdirSync(targetPatchDir, { recursive: true, mode: 0o700 });
|
|
152
|
+
writeFileSync(join(targetPatchDir, binaryName), binaryBytes, { mode: 0o700 });
|
|
153
|
+
writeFileSync(join(targetPatchDir, 'manifest.json'), manifestBytes, { mode: 0o600 });
|
|
154
|
+
return true;
|
|
155
|
+
} catch {
|
|
156
|
+
// A missing, stale, or malformed host cache is optional acceleration only.
|
|
157
|
+
// Leave the pristine runtime empty so the normal fetch path handles it.
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function auditDocument({
|
|
163
|
+
provider,
|
|
164
|
+
model,
|
|
165
|
+
effort,
|
|
166
|
+
fast,
|
|
167
|
+
configBytes,
|
|
168
|
+
authMode,
|
|
169
|
+
catalogCount,
|
|
170
|
+
}) {
|
|
171
|
+
const contract = PRISTINE_EXECUTION_CONTRACT;
|
|
172
|
+
return {
|
|
173
|
+
schemaVersion: contract.schemaVersion,
|
|
174
|
+
mode: 'headless-role-pristine',
|
|
175
|
+
provider: clean(provider),
|
|
176
|
+
model: clean(model),
|
|
177
|
+
effort: clean(effort).toLowerCase() || null,
|
|
178
|
+
fast: fast === true,
|
|
179
|
+
configSha256: createHash('sha256').update(configBytes).digest('hex'),
|
|
180
|
+
authMode,
|
|
181
|
+
authArtifactFilesCopied: 0,
|
|
182
|
+
injectedModelCatalogFileCount: catalogCount,
|
|
183
|
+
personalState: {
|
|
184
|
+
hostConfigRead: false,
|
|
185
|
+
...Object.fromEntries(contract.personalStateCounters.map((name) => [name, 0])),
|
|
186
|
+
},
|
|
187
|
+
featuresEnabled: Object.fromEntries(
|
|
188
|
+
contract.disabledFeatures.map((name) => [name, false]),
|
|
189
|
+
),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function formatPristineExecutionAudit(audit) {
|
|
194
|
+
return [
|
|
195
|
+
`pristine-execution-audit v${audit.schemaVersion}`,
|
|
196
|
+
'mode=headless-role',
|
|
197
|
+
'personal-files=0',
|
|
198
|
+
'host-config=0',
|
|
199
|
+
'mcp=0',
|
|
200
|
+
'skills=0',
|
|
201
|
+
'core-memory=0',
|
|
202
|
+
'channels=0',
|
|
203
|
+
'plugins=0',
|
|
204
|
+
'profiles=0',
|
|
205
|
+
'prior-sessions=0',
|
|
206
|
+
`provider=${audit.provider}`,
|
|
207
|
+
`model=${audit.model}`,
|
|
208
|
+
`effort=${audit.effort || 'provider-default'}`,
|
|
209
|
+
`fast=${String(audit.fast)}`,
|
|
210
|
+
`auth=${audit.authMode}`,
|
|
211
|
+
`catalogs=${audit.injectedModelCatalogFileCount}`,
|
|
212
|
+
].join(' ');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function createPristineExecutionBoundary({
|
|
216
|
+
provider,
|
|
217
|
+
model,
|
|
218
|
+
effort,
|
|
219
|
+
fast,
|
|
220
|
+
env = process.env,
|
|
221
|
+
approvedExecutionEnv = {},
|
|
222
|
+
apiKeyResolver = getAgentApiKey,
|
|
223
|
+
} = {}) {
|
|
224
|
+
const routeError = validateExplicitPristineRoute({ provider, model, effort, fast });
|
|
225
|
+
if (routeError) throw new Error(routeError);
|
|
226
|
+
|
|
227
|
+
const contract = PRISTINE_EXECUTION_CONTRACT;
|
|
228
|
+
const hostEnv = { ...env };
|
|
229
|
+
const originalEnv = new Map();
|
|
230
|
+
const touchedKeys = new Set();
|
|
231
|
+
const setEnv = (name, value) => {
|
|
232
|
+
if (!touchedKeys.has(name)) {
|
|
233
|
+
originalEnv.set(name, Object.prototype.hasOwnProperty.call(env, name) ? env[name] : undefined);
|
|
234
|
+
touchedKeys.add(name);
|
|
235
|
+
}
|
|
236
|
+
env[name] = String(value);
|
|
237
|
+
};
|
|
238
|
+
const unsetEnv = (name) => {
|
|
239
|
+
if (!touchedKeys.has(name)) {
|
|
240
|
+
originalEnv.set(name, Object.prototype.hasOwnProperty.call(env, name) ? env[name] : undefined);
|
|
241
|
+
touchedKeys.add(name);
|
|
242
|
+
}
|
|
243
|
+
delete env[name];
|
|
244
|
+
};
|
|
245
|
+
const rootDir = mkdtempSync(join(tmpdir(), 'mixdog-headless-pristine-'));
|
|
246
|
+
const dataDir = join(rootDir, 'data');
|
|
247
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
248
|
+
let cleaned = false;
|
|
249
|
+
let restoreAuthBindings = () => {};
|
|
250
|
+
const cleanup = () => {
|
|
251
|
+
if (cleaned) return;
|
|
252
|
+
cleaned = true;
|
|
253
|
+
for (const [name, value] of originalEnv) {
|
|
254
|
+
if (value === undefined) delete env[name];
|
|
255
|
+
else env[name] = value;
|
|
256
|
+
}
|
|
257
|
+
restoreAuthBindings();
|
|
258
|
+
rmSync(rootDir, { recursive: true, force: true });
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const sourceDataDir = hostDataDir(hostEnv);
|
|
263
|
+
seedVerifiedPatchBinaryCache(sourceDataDir, dataDir);
|
|
264
|
+
const approvedNames = new Set(contract.approvedExecutionEnv || []);
|
|
265
|
+
for (const name of Object.keys(approvedExecutionEnv || {})) {
|
|
266
|
+
if (!approvedNames.has(name)) {
|
|
267
|
+
throw new Error(`unapproved pristine execution environment override: ${name}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const inheritedExecutionEnv = Object.fromEntries(
|
|
271
|
+
[...approvedNames]
|
|
272
|
+
.filter((name) => hostEnv[name] !== undefined)
|
|
273
|
+
.map((name) => [name, hostEnv[name]]),
|
|
274
|
+
);
|
|
275
|
+
const selectedProvider = clean(provider);
|
|
276
|
+
const oauth = contract.oauthProviders[selectedProvider];
|
|
277
|
+
const apiKeyEnv = contract.apiKeyProviders[selectedProvider];
|
|
278
|
+
let selectedApiKey = null;
|
|
279
|
+
if (apiKeyEnv) {
|
|
280
|
+
// Resolve before scrubbing so the selected provider may use its standard,
|
|
281
|
+
// MIXDOG_AGENT_*, alias, or one provider-scoped keychain account. No
|
|
282
|
+
// general config loader or all-provider credential scan is involved.
|
|
283
|
+
selectedApiKey = clean(apiKeyResolver(selectedProvider));
|
|
284
|
+
}
|
|
285
|
+
// Fail closed: no inherited Mixdog behavior reaches the child runtime.
|
|
286
|
+
// Only contract-listed operational controls are reintroduced below.
|
|
287
|
+
for (const name of Object.keys(env)) {
|
|
288
|
+
if (name.startsWith('MIXDOG_')) unsetEnv(name);
|
|
289
|
+
}
|
|
290
|
+
for (const entry of Object.values(contract.oauthProviders)) {
|
|
291
|
+
unsetEnv(entry.credentialPathEnv);
|
|
292
|
+
}
|
|
293
|
+
for (const name of Object.values(contract.apiKeyProviders)) unsetEnv(name);
|
|
294
|
+
for (const aliases of Object.values(AGENT_PROVIDER_ENV_ALIASES)) {
|
|
295
|
+
for (const name of aliases) unsetEnv(name);
|
|
296
|
+
}
|
|
297
|
+
setEnv('MIXDOG_HOME', rootDir);
|
|
298
|
+
setEnv('MIXDOG_DATA_DIR', dataDir);
|
|
299
|
+
for (const [name, value] of Object.entries({
|
|
300
|
+
...inheritedExecutionEnv,
|
|
301
|
+
...approvedExecutionEnv,
|
|
302
|
+
})) {
|
|
303
|
+
setEnv(name, value);
|
|
304
|
+
}
|
|
305
|
+
for (const [name, value] of Object.entries(contract.guardEnv)) setEnv(name, value);
|
|
306
|
+
|
|
307
|
+
let authMode = 'provider-managed';
|
|
308
|
+
let catalogCount = 0;
|
|
309
|
+
if (oauth) {
|
|
310
|
+
const sourceCredential = clean(hostEnv[oauth.credentialPathEnv])
|
|
311
|
+
? resolve(hostEnv[oauth.credentialPathEnv])
|
|
312
|
+
: join(sourceDataDir, oauth.credentialFile);
|
|
313
|
+
if (!existsSync(sourceCredential)) {
|
|
314
|
+
throw new Error(`required ${clean(provider)} credentials are unavailable; sign in before using the headless role command`);
|
|
315
|
+
}
|
|
316
|
+
// Authentication is the sole host-state exception. Bind in-process (not
|
|
317
|
+
// via child-visible environment) so tools cannot discover the auth path.
|
|
318
|
+
restoreAuthBindings = replaceProviderAuthBindings({
|
|
319
|
+
[selectedProvider]: sourceCredential,
|
|
320
|
+
});
|
|
321
|
+
authMode = 'in-process-host-oauth-binding';
|
|
322
|
+
const sourceCatalog = join(sourceDataDir, oauth.modelCatalogFile);
|
|
323
|
+
if (existsSync(sourceCatalog)) {
|
|
324
|
+
copyFileSync(sourceCatalog, join(dataDir, oauth.modelCatalogFile));
|
|
325
|
+
catalogCount = 1;
|
|
326
|
+
}
|
|
327
|
+
} else if (apiKeyEnv) {
|
|
328
|
+
if (!selectedApiKey) {
|
|
329
|
+
throw new Error(`required ${selectedProvider} authentication is unavailable; set ${apiKeyEnv}`);
|
|
330
|
+
}
|
|
331
|
+
authMode = 'in-process-api-key';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const config = buildMinimalPristineConfig({ provider, model, effort, fast });
|
|
335
|
+
const loadConfig = createSelectedProviderPristineLoader({
|
|
336
|
+
config,
|
|
337
|
+
provider,
|
|
338
|
+
apiKey: selectedApiKey,
|
|
339
|
+
});
|
|
340
|
+
const configBytes = `${JSON.stringify(config, null, 2)}\n`;
|
|
341
|
+
writeFileSync(join(dataDir, contract.configFileName), configBytes, { mode: 0o600 });
|
|
342
|
+
const audit = auditDocument({
|
|
343
|
+
provider,
|
|
344
|
+
model,
|
|
345
|
+
effort,
|
|
346
|
+
fast,
|
|
347
|
+
configBytes,
|
|
348
|
+
authMode,
|
|
349
|
+
catalogCount,
|
|
350
|
+
});
|
|
351
|
+
return { rootDir, dataDir, config, audit, loadConfig, cleanup };
|
|
352
|
+
} catch (error) {
|
|
353
|
+
cleanup();
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const { getSecret } = require('../../lib/keychain-cjs.cjs');
|
|
5
|
+
|
|
6
|
+
// Lightweight API-key lookup shared by pristine startup and the general config
|
|
7
|
+
// loader. This module deliberately has no mixdog-config.json dependency: asking
|
|
8
|
+
// for one provider probes only that provider's environment/keychain account.
|
|
9
|
+
export const AGENT_PROVIDER_ENV = Object.freeze({
|
|
10
|
+
openai: 'OPENAI_API_KEY',
|
|
11
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
12
|
+
gemini: 'GEMINI_API_KEY',
|
|
13
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
14
|
+
xai: 'XAI_API_KEY',
|
|
15
|
+
'opencode-go': 'OPENCODE_API_KEY',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const AGENT_PROVIDER_ENV_ALIASES = Object.freeze({
|
|
19
|
+
xai: ['GROK_API_KEY'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function providerSecretEnv(provider) {
|
|
23
|
+
return `MIXDOG_AGENT_${String(provider || '').replace(/[.\s]+/g, '_').toUpperCase()}_APIKEY`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getAgentApiKey(provider) {
|
|
27
|
+
const id = String(provider || '').trim();
|
|
28
|
+
const standardEnv = AGENT_PROVIDER_ENV[id];
|
|
29
|
+
if (standardEnv && process.env[standardEnv]) return process.env[standardEnv];
|
|
30
|
+
const mixdogEnv = providerSecretEnv(id);
|
|
31
|
+
if (process.env[mixdogEnv]) return process.env[mixdogEnv];
|
|
32
|
+
try {
|
|
33
|
+
const stored = getSecret(`agent.${id}.apiKey`);
|
|
34
|
+
if (stored) return stored;
|
|
35
|
+
} catch {
|
|
36
|
+
// A missing/locked keychain is equivalent to no stored key. Explicit
|
|
37
|
+
// environment aliases remain available as the final provider-local source.
|
|
38
|
+
}
|
|
39
|
+
for (const alias of AGENT_PROVIDER_ENV_ALIASES[id] || []) {
|
|
40
|
+
if (process.env[alias]) return process.env[alias];
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const AUTH_BINDINGS = Symbol.for('mixdog.providerAuthBindings');
|
|
2
|
+
|
|
3
|
+
export function boundProviderAuthPath(provider) {
|
|
4
|
+
const bindings = globalThis[AUTH_BINDINGS];
|
|
5
|
+
const value = bindings && typeof bindings === 'object'
|
|
6
|
+
? bindings[String(provider || '').trim()]
|
|
7
|
+
: null;
|
|
8
|
+
return typeof value === 'string' && value ? value : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function replaceProviderAuthBindings(next = {}) {
|
|
12
|
+
const previous = globalThis[AUTH_BINDINGS];
|
|
13
|
+
globalThis[AUTH_BINDINGS] = Object.freeze({ ...next });
|
|
14
|
+
let restored = false;
|
|
15
|
+
return () => {
|
|
16
|
+
if (restored) return;
|
|
17
|
+
restored = true;
|
|
18
|
+
if (previous === undefined) delete globalThis[AUTH_BINDINGS];
|
|
19
|
+
else globalThis[AUTH_BINDINGS] = previous;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -7,8 +7,30 @@ import {
|
|
|
7
7
|
summarizeContextMessages,
|
|
8
8
|
toolSchemaSignature,
|
|
9
9
|
} from '../runtime/agent/orchestrator/session/context-utils.mjs';
|
|
10
|
+
import { resolveWorkerCompactPolicy } from '../runtime/agent/orchestrator/session/loop/compact-policy.mjs';
|
|
10
11
|
import { estimateToolSchemaBreakdown } from './tool-catalog.mjs';
|
|
11
12
|
|
|
13
|
+
const DEFERRED_CATALOG_TOOL_PROVIDERS = new Set(['anthropic', 'anthropic-oauth']);
|
|
14
|
+
|
|
15
|
+
// Mirrors the tool-list portion of the Anthropic adapters without changing
|
|
16
|
+
// their wire serialization. Other native-deferred providers expose the
|
|
17
|
+
// catalog through BP1/system content, which is already metered there.
|
|
18
|
+
export function requestSerializedToolsForContext(session, provider) {
|
|
19
|
+
const activeTools = Array.isArray(session?.tools) ? session.tools : [];
|
|
20
|
+
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
|
21
|
+
if (!DEFERRED_CATALOG_TOOL_PROVIDERS.has(normalizedProvider)
|
|
22
|
+
|| session?.deferredNativeTools !== true
|
|
23
|
+
|| activeTools.length === 0) {
|
|
24
|
+
return activeTools;
|
|
25
|
+
}
|
|
26
|
+
const activeNames = new Set(activeTools.map((tool) => String(tool?.name || '').trim()).filter(Boolean));
|
|
27
|
+
const catalog = Array.isArray(session?.deferredToolCatalog) ? session.deferredToolCatalog : [];
|
|
28
|
+
const deferredTools = catalog
|
|
29
|
+
.filter((tool) => tool?.name && !activeNames.has(String(tool.name)))
|
|
30
|
+
.map((tool) => ({ ...tool, deferLoading: true }));
|
|
31
|
+
return deferredTools.length ? [...activeTools, ...deferredTools] : activeTools;
|
|
32
|
+
}
|
|
33
|
+
|
|
12
34
|
// Live /context gauge computation + its self-owned memoization cache. Extracted
|
|
13
35
|
// verbatim from the runtime API object; the runtime injects live getters for
|
|
14
36
|
// the mutable session/route/cwd/mode locals. The cache (key + value) is owned
|
|
@@ -18,7 +40,15 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
18
40
|
let contextStatusCacheKey = null;
|
|
19
41
|
let contextStatusCacheValue = null;
|
|
20
42
|
|
|
21
|
-
function contextStatusCacheKeyFor({
|
|
43
|
+
function contextStatusCacheKeyFor({
|
|
44
|
+
messages,
|
|
45
|
+
tools,
|
|
46
|
+
messagesRevision,
|
|
47
|
+
toolsSignature,
|
|
48
|
+
requestProvider,
|
|
49
|
+
requestToolCount,
|
|
50
|
+
requestToolsSignature,
|
|
51
|
+
}) {
|
|
22
52
|
const session = getSession();
|
|
23
53
|
const route = getRoute();
|
|
24
54
|
const compaction = session?.compaction || {};
|
|
@@ -39,6 +69,9 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
39
69
|
tools,
|
|
40
70
|
toolCount: tools.length,
|
|
41
71
|
toolsSignature,
|
|
72
|
+
requestProvider,
|
|
73
|
+
requestToolCount,
|
|
74
|
+
requestToolsSignature,
|
|
42
75
|
contextWindow: session?.contextWindow || null,
|
|
43
76
|
rawContextWindow: session?.rawContextWindow || null,
|
|
44
77
|
effectiveContextWindowPercent: session?.effectiveContextWindowPercent || null,
|
|
@@ -89,22 +122,33 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
89
122
|
const liveTurnMessages = Array.isArray(session?.liveTurnMessages) ? session.liveTurnMessages : null;
|
|
90
123
|
const messages = liveTurnMessages || (Array.isArray(session?.messages) ? session.messages : []);
|
|
91
124
|
const tools = Array.isArray(session?.tools) ? session.tools : [];
|
|
125
|
+
const requestProvider = session?.provider || route.provider;
|
|
126
|
+
const requestTools = requestSerializedToolsForContext(session, requestProvider);
|
|
92
127
|
const messagesRevision = contextMessagesRevision(messages);
|
|
93
128
|
const toolsSignature = toolSchemaSignature(tools);
|
|
94
|
-
const
|
|
129
|
+
const requestToolsSignature = toolSchemaSignature(requestTools);
|
|
130
|
+
const cacheKey = contextStatusCacheKeyFor({
|
|
131
|
+
messages,
|
|
132
|
+
tools,
|
|
133
|
+
messagesRevision,
|
|
134
|
+
toolsSignature,
|
|
135
|
+
requestProvider,
|
|
136
|
+
requestToolCount: requestTools.length,
|
|
137
|
+
requestToolsSignature,
|
|
138
|
+
});
|
|
95
139
|
if (contextStatusCacheValue && sameContextStatusCacheKey(cacheKey, contextStatusCacheKey)) {
|
|
96
140
|
return contextStatusCacheValue;
|
|
97
141
|
}
|
|
98
142
|
|
|
99
143
|
const messageSummary = summarizeContextMessages(messages);
|
|
100
|
-
const toolSchemaTokens = estimateToolSchemaTokens(
|
|
101
|
-
const toolSchemaBreakdown = estimateToolSchemaBreakdown(
|
|
102
|
-
const requestReserveTokens = estimateRequestReserveTokens(
|
|
144
|
+
const toolSchemaTokens = estimateToolSchemaTokens(requestTools);
|
|
145
|
+
const toolSchemaBreakdown = estimateToolSchemaBreakdown(requestTools);
|
|
146
|
+
const requestReserveTokens = estimateRequestReserveTokens(requestTools);
|
|
103
147
|
const requestOverheadTokens = Math.max(0, requestReserveTokens - toolSchemaTokens);
|
|
104
148
|
const rawWindow = Number(session?.rawContextWindow || session?.contextWindow || 0);
|
|
105
149
|
const effectiveWindow = Number(session?.contextWindow || rawWindow || 0);
|
|
106
150
|
const lastContextTokens = Number(session?.lastContextTokens || 0);
|
|
107
|
-
const estimatedContextTokens = estimateTranscriptContextUsage(messages,
|
|
151
|
+
const estimatedContextTokens = estimateTranscriptContextUsage(messages, requestTools, {
|
|
108
152
|
messageCount: messageSummary.count,
|
|
109
153
|
estimatedMessageTokens: messageSummary.estimatedTokens,
|
|
110
154
|
});
|
|
@@ -125,15 +169,18 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
125
169
|
// the gauge numerator.
|
|
126
170
|
const usedTokens = estimatedContextTokens;
|
|
127
171
|
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
128
|
-
// Use the
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
172
|
+
// Use the worker policy when a boundary is available so target/reserve
|
|
173
|
+
// headroom, trigger, buffer tokens, and buffer ratio stay identical to the
|
|
174
|
+
// auto-compact decision. Fall back only for incomplete session metadata.
|
|
175
|
+
const workerCompactPolicy = resolveWorkerCompactPolicy(session, tools);
|
|
176
|
+
const compactPolicy = workerCompactPolicy?.boundaryTokens
|
|
177
|
+
? workerCompactPolicy
|
|
178
|
+
: resolveSessionCompactPolicy(session || {}, compactBoundaryTokens);
|
|
135
179
|
const compactTriggerTokens = compactPolicy.triggerTokens || 0;
|
|
136
180
|
const compactBufferTokens = compactPolicy.bufferTokens || 0;
|
|
181
|
+
const compactBufferRatio = Number.isFinite(compactPolicy.bufferRatio)
|
|
182
|
+
? compactPolicy.bufferRatio
|
|
183
|
+
: null;
|
|
137
184
|
const value = {
|
|
138
185
|
sessionId: session?.id || null,
|
|
139
186
|
provider: route.provider,
|
|
@@ -155,6 +202,7 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
155
202
|
boundaryTokens: compactBoundaryTokens || null,
|
|
156
203
|
triggerTokens: compactTriggerTokens || null,
|
|
157
204
|
bufferTokens: compactBufferTokens || null,
|
|
205
|
+
bufferRatio: compactBufferRatio,
|
|
158
206
|
currentEstimatedTokens: estimatedContextTokens,
|
|
159
207
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
160
208
|
lastApiRequestStale: lastUsageStale,
|