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.
Files changed (116) hide show
  1. package/package.json +5 -2
  2. package/scripts/agent-dispatch-abort-compose-test.mjs +31 -0
  3. package/scripts/agent-model-liveness-test.mjs +618 -0
  4. package/scripts/agent-trace-io-test.mjs +68 -4
  5. package/scripts/bench-run.mjs +30 -3
  6. package/scripts/compact-pressure-test.mjs +187 -4
  7. package/scripts/compact-prior-context-flatten-test.mjs +252 -0
  8. package/scripts/compact-trigger-migration-smoke.mjs +8 -6
  9. package/scripts/compacted-placeholder-scrub-test.mjs +20 -34
  10. package/scripts/context-mcp-metering-test.mjs +75 -0
  11. package/scripts/explore-prompt-policy-test.mjs +15 -16
  12. package/scripts/explore-timeout-cancel-test.mjs +345 -0
  13. package/scripts/headless-pristine-execution-test.mjs +614 -0
  14. package/scripts/internal-comms-smoke.mjs +15 -6
  15. package/scripts/live-worker-smoke.mjs +1 -1
  16. package/scripts/memory-core-input-test.mjs +137 -0
  17. package/scripts/memory-rule-contract-test.mjs +5 -5
  18. package/scripts/openai-oauth-refresh-race-test.mjs +120 -0
  19. package/scripts/openai-oauth-ws-1006-retry-test.mjs +26 -0
  20. package/scripts/parent-abort-link-test.mjs +22 -0
  21. package/scripts/provider-toolcall-test.mjs +22 -0
  22. package/scripts/reactive-compact-persist-smoke.mjs +8 -4
  23. package/scripts/session-bench.mjs +3 -70
  24. package/scripts/task-bench.mjs +0 -2
  25. package/scripts/terminal-bench-isolation-guards-test.mjs +102 -0
  26. package/scripts/tool-smoke.mjs +21 -21
  27. package/scripts/tool-tui-presentation-test.mjs +68 -0
  28. package/scripts/tui-transcript-jitter-harness-entry.jsx +91 -10
  29. package/src/app.mjs +28 -103
  30. package/src/cli.mjs +17 -13
  31. package/src/headless-command.mjs +139 -0
  32. package/src/headless-role.mjs +121 -10
  33. package/src/help.mjs +4 -1
  34. package/src/rules/agent/00-common.md +3 -3
  35. package/src/rules/agent/00-core.md +8 -9
  36. package/src/rules/agent/20-skip-protocol.md +2 -3
  37. package/src/rules/agent/30-explorer.md +50 -56
  38. package/src/rules/agent/40-cycle1-agent.md +10 -12
  39. package/src/rules/agent/41-cycle2-agent.md +12 -9
  40. package/src/rules/agent/42-cycle3-agent.md +4 -6
  41. package/src/rules/lead/01-general.md +5 -6
  42. package/src/rules/lead/02-channels.md +1 -1
  43. package/src/rules/lead/lead-brief.md +14 -17
  44. package/src/rules/lead/lead-tool.md +3 -3
  45. package/src/rules/shared/01-tool.md +41 -43
  46. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +46 -10
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +44 -31
  48. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +18 -3
  49. package/src/runtime/agent/orchestrator/config.mjs +96 -30
  50. package/src/runtime/agent/orchestrator/context/collect.mjs +9 -0
  51. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  52. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +18 -5
  53. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -6
  54. package/src/runtime/agent/orchestrator/providers/gemini.mjs +6 -6
  55. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +123 -3
  56. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -3
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +148 -30
  58. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +5 -7
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +62 -14
  60. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +141 -19
  61. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -4
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +19 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +85 -17
  64. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -8
  65. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -17
  66. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +159 -20
  67. package/src/runtime/agent/orchestrator/session/context-utils.mjs +83 -10
  68. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +75 -7
  69. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +4 -374
  70. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +0 -75
  71. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +0 -5
  72. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +20 -3
  73. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +32 -16
  74. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +86 -15
  76. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +30 -27
  77. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +1 -14
  78. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +21 -27
  79. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +1 -3
  80. package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -51
  81. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
  82. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +26 -0
  84. package/src/runtime/memory/index.mjs +0 -1
  85. package/src/runtime/memory/lib/core-memory-store.mjs +44 -24
  86. package/src/runtime/memory/lib/http-router.mjs +0 -193
  87. package/src/runtime/memory/lib/memory-action-handlers.mjs +37 -11
  88. package/src/runtime/memory/tool-defs.mjs +5 -6
  89. package/src/runtime/shared/config.mjs +11 -34
  90. package/src/runtime/shared/pristine-execution-contract.json +75 -0
  91. package/src/runtime/shared/pristine-execution.mjs +356 -0
  92. package/src/runtime/shared/provider-api-key.mjs +43 -0
  93. package/src/runtime/shared/provider-auth-binding.mjs +21 -0
  94. package/src/session-runtime/context-status.mjs +61 -13
  95. package/src/session-runtime/mcp-glue.mjs +29 -2
  96. package/src/session-runtime/plugin-mcp.mjs +7 -0
  97. package/src/session-runtime/resource-api.mjs +38 -5
  98. package/src/session-runtime/runtime-core.mjs +5 -1
  99. package/src/session-runtime/session-turn-api.mjs +14 -2
  100. package/src/session-runtime/settings-api.mjs +5 -0
  101. package/src/session-runtime/tool-catalog.mjs +13 -2
  102. package/src/session-runtime/tool-defs.mjs +1 -3
  103. package/src/standalone/agent-task-status.mjs +50 -11
  104. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  105. package/src/standalone/explore-tool.mjs +257 -49
  106. package/src/standalone/seeds.mjs +1 -0
  107. package/src/tui/App.jsx +23 -10
  108. package/src/tui/app/use-transcript-scroll.mjs +4 -3
  109. package/src/tui/app/use-transcript-window.mjs +12 -21
  110. package/src/tui/components/ContextPanel.jsx +19 -25
  111. package/src/tui/dist/index.mjs +77 -65
  112. package/src/tui/engine/agent-envelope.mjs +16 -5
  113. package/src/tui/engine/labels.mjs +1 -1
  114. package/src/workflows/default/WORKFLOW.md +21 -51
  115. package/src/workflows/solo/WORKFLOW.md +12 -17
  116. package/src/workflows/solo-review/WORKFLOW.md +0 -47
@@ -0,0 +1,614 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { execFileSync, spawn, spawnSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
5
+ import {
6
+ existsSync,
7
+ mkdirSync,
8
+ mkdtempSync,
9
+ readFileSync,
10
+ readdirSync,
11
+ rmSync,
12
+ writeFileSync,
13
+ } from 'node:fs';
14
+ import { tmpdir } from 'node:os';
15
+ import { join, resolve } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ import { HELP_LINES } from '../src/help.mjs';
19
+ import { parseHeadlessRoleCommand } from '../src/app.mjs';
20
+ import { classifyCliInvocation } from '../src/headless-command.mjs';
21
+ import { runHeadlessRole } from '../src/headless-role.mjs';
22
+ import { boundProviderAuthPath } from '../src/runtime/shared/provider-auth-binding.mjs';
23
+ import { AGENT_PROVIDER_ENV_ALIASES } from '../src/runtime/shared/provider-api-key.mjs';
24
+ import {
25
+ PRISTINE_EXECUTION_CONTRACT,
26
+ buildMinimalPristineConfig,
27
+ createPristineExecutionBoundary,
28
+ seedVerifiedPatchBinaryCache,
29
+ validateExplicitPristineRoute,
30
+ } from '../src/runtime/shared/pristine-execution.mjs';
31
+
32
+ const REPO_ROOT = fileURLToPath(new URL('..', import.meta.url));
33
+
34
+ function fixtureHost() {
35
+ const root = mkdtempSync(join(tmpdir(), 'mixdog-headless-host-'));
36
+ const data = join(root, 'data');
37
+ mkdirSync(data, { recursive: true });
38
+ writeFileSync(join(data, 'openai-oauth.json'), JSON.stringify({
39
+ access_token: 'access-secret',
40
+ refresh_token: 'refresh-secret',
41
+ }));
42
+ writeFileSync(join(data, 'openai-oauth-models.json'), '{"models":[]}');
43
+ writeFileSync(join(data, 'mixdog-config.json'), '{"personalBehavior":"must-not-load"}');
44
+ for (const relative of [
45
+ 'skills/personal/SKILL.md',
46
+ 'sessions/prior.json',
47
+ 'plugins/registry.json',
48
+ 'memory/core.json',
49
+ 'profiles/personal.json',
50
+ 'channels/discord.json',
51
+ ]) {
52
+ const path = join(data, relative);
53
+ mkdirSync(resolve(path, '..'), { recursive: true });
54
+ writeFileSync(path, 'personal-secret');
55
+ }
56
+ return { root, data };
57
+ }
58
+
59
+ test('headless command parsing remains role-focused while route validation is explicit', () => {
60
+ assert.deepEqual(
61
+ parseHeadlessRoleCommand(['--provider', 'openai-oauth', '--model', 'gpt-x', 'review', 'check', 'it']),
62
+ { agent: 'reviewer', message: 'check it' },
63
+ );
64
+ assert.match(validateExplicitPristineRoute({}), /require both --provider/);
65
+ assert.match(
66
+ validateExplicitPristineRoute({ provider: 'openai-oauth' }),
67
+ /host route fallback is disabled/,
68
+ );
69
+ assert.equal(
70
+ validateExplicitPristineRoute({ provider: 'openai-oauth', model: 'gpt-x', effort: 'high', fast: true }),
71
+ null,
72
+ );
73
+ assert.ok(HELP_LINES.some((line) => line.includes('--provider <name> --model <name>')));
74
+ assert.ok(HELP_LINES.some((line) => line.includes('host behavioral config')));
75
+ const missingProviderValue = classifyCliInvocation([
76
+ '--provider', '--model', 'gpt-x', 'worker', 'fixture',
77
+ ]);
78
+ assert.equal(missingProviderValue.kind, 'error');
79
+ assert.equal(missingProviderValue.skipHostPrelude, true);
80
+ assert.match(missingProviderValue.error, /--provider requires a non-option value/);
81
+ const missingModelValue = classifyCliInvocation([
82
+ '--provider', 'openai-oauth', '--model', '--fast', 'worker', 'fixture',
83
+ ]);
84
+ assert.equal(missingModelValue.kind, 'error');
85
+ assert.equal(missingModelValue.skipHostPrelude, true);
86
+ assert.match(missingModelValue.error, /--model requires a non-option value/);
87
+ const roleConsumedAsModel = classifyCliInvocation([
88
+ '--provider', 'openai-oauth', '--model', 'worker', 'fixture',
89
+ ]);
90
+ assert.equal(roleConsumedAsModel.kind, 'error');
91
+ assert.equal(roleConsumedAsModel.skipHostPrelude, true);
92
+ assert.match(roleConsumedAsModel.error, /route value before headless role/);
93
+ const roleConsumedAsEffort = classifyCliInvocation([
94
+ '--provider', 'openai-oauth', '--model', 'gpt-x', '--effort', 'worker', 'fixture',
95
+ ]);
96
+ assert.equal(roleConsumedAsEffort.kind, 'error');
97
+ assert.equal(roleConsumedAsEffort.skipHostPrelude, true);
98
+ assert.match(roleConsumedAsEffort.error, /--effort requires a route value before headless role/);
99
+ const unknownHeadlessOption = classifyCliInvocation([
100
+ '--provider', 'openai-oauth', '--model', 'gpt-x', 'worker', 'fixture', '--bogus',
101
+ ]);
102
+ assert.equal(unknownHeadlessOption.kind, 'error');
103
+ assert.equal(unknownHeadlessOption.skipHostPrelude, true);
104
+ assert.match(unknownHeadlessOption.error, /unknown option --bogus/);
105
+ const unknownBeforeHeadlessRole = classifyCliInvocation([
106
+ '--provider', 'openai', '--model', 'gpt', '--bogus', 'worker', 'task',
107
+ ]);
108
+ assert.equal(unknownBeforeHeadlessRole.kind, 'error');
109
+ assert.equal(unknownBeforeHeadlessRole.skipHostPrelude, true);
110
+ assert.match(unknownBeforeHeadlessRole.error, /unknown option --bogus/);
111
+ const forbiddenWorkflow = classifyCliInvocation([
112
+ '--provider', 'openai-oauth', '--model', 'gpt-x', '--workflow', 'default', 'worker', 'fixture',
113
+ ]);
114
+ assert.equal(forbiddenWorkflow.kind, 'error');
115
+ assert.equal(forbiddenWorkflow.skipHostPrelude, true);
116
+ assert.match(forbiddenWorkflow.error, /--workflow is not supported for headless role commands/);
117
+ const workflowMustNotConsumeRole = classifyCliInvocation(['--workflow', 'worker', 'task']);
118
+ assert.equal(workflowMustNotConsumeRole.kind, 'error');
119
+ assert.equal(workflowMustNotConsumeRole.skipHostPrelude, true);
120
+ assert.match(workflowMustNotConsumeRole.error, /--workflow is not supported for headless role commands/);
121
+ const ordinaryGeneral = classifyCliInvocation(['chat', 'task']);
122
+ assert.equal(ordinaryGeneral.kind, 'general');
123
+ assert.equal(ordinaryGeneral.skipHostPrelude, undefined);
124
+ const ordinaryUnknownOption = classifyCliInvocation(['--bogus', 'chat', 'task']);
125
+ assert.equal(ordinaryUnknownOption.kind, 'error');
126
+ assert.equal(ordinaryUnknownOption.skipHostPrelude, undefined);
127
+ const plainConflict = classifyCliInvocation(['--plain', 'worker', 'fixture']);
128
+ assert.equal(plainConflict.kind, 'plain');
129
+ assert.equal(plainConflict.skipHostPrelude, undefined);
130
+ assert.equal(parseHeadlessRoleCommand(['--plain', 'worker', 'fixture']), null);
131
+ const plainUnknownOption = classifyCliInvocation(['--plain', 'worker', 'fixture', '--bogus']);
132
+ assert.equal(plainUnknownOption.kind, 'error');
133
+ assert.equal(plainUnknownOption.skipHostPrelude, undefined);
134
+ const reactWorkflowRole = classifyCliInvocation(['--react', '--workflow', 'worker', 'task']);
135
+ assert.equal(reactWorkflowRole.kind, 'react');
136
+ assert.equal(reactWorkflowRole.skipHostPrelude, undefined);
137
+ });
138
+
139
+ test('boundary binds only provider auth, copies only its catalog, and cleans all ephemeral state', () => {
140
+ const host = fixtureHost();
141
+ const previous = {
142
+ data: process.env.MIXDOG_DATA_DIR,
143
+ home: process.env.MIXDOG_HOME,
144
+ auth: process.env.OPENAI_OAUTH_CREDENTIALS_PATH,
145
+ behavior: process.env.MIXDOG_PERSONAL_BEHAVIOR,
146
+ debug: process.env.MIXDOG_DEBUG_AGENT,
147
+ anthropic: process.env.ANTHROPIC_API_KEY,
148
+ };
149
+ process.env.MIXDOG_DATA_DIR = host.data;
150
+ process.env.MIXDOG_PERSONAL_BEHAVIOR = 'must-be-scrubbed';
151
+ process.env.MIXDOG_DEBUG_AGENT = 'must-be-scrubbed';
152
+ process.env.ANTHROPIC_API_KEY = 'unrelated-api-secret';
153
+ delete process.env.OPENAI_OAUTH_CREDENTIALS_PATH;
154
+ let boundary;
155
+ try {
156
+ boundary = createPristineExecutionBoundary({
157
+ provider: 'openai-oauth',
158
+ model: 'gpt-explicit',
159
+ effort: 'xhigh',
160
+ fast: true,
161
+ });
162
+ const names = readdirSync(boundary.dataDir).sort();
163
+ assert.deepEqual(names, [
164
+ 'mixdog-config.json',
165
+ 'openai-oauth-models.json',
166
+ ]);
167
+ assert.equal(process.env.OPENAI_OAUTH_CREDENTIALS_PATH, undefined);
168
+ assert.equal(
169
+ boundProviderAuthPath('openai-oauth'),
170
+ join(host.data, 'openai-oauth.json'),
171
+ );
172
+ assert.equal(process.env.MIXDOG_PERSONAL_BEHAVIOR, undefined);
173
+ assert.equal(process.env.MIXDOG_DEBUG_AGENT, undefined);
174
+ assert.equal(process.env.ANTHROPIC_API_KEY, undefined);
175
+ assert.equal(process.env.MIXDOG_DISABLE_MCP, '1');
176
+ assert.equal(process.env.MIXDOG_DISABLE_SKILLS, '1');
177
+ assert.equal(process.env.MIXDOG_BOOT_CORE_MEMORY, '0');
178
+ assert.equal(process.env.MIXDOG_DISABLE_CHANNEL_START, '1');
179
+ assert.deepEqual(
180
+ JSON.parse(readFileSync(join(boundary.dataDir, 'mixdog-config.json'), 'utf8')),
181
+ buildMinimalPristineConfig({
182
+ provider: 'openai-oauth',
183
+ model: 'gpt-explicit',
184
+ effort: 'xhigh',
185
+ fast: true,
186
+ }),
187
+ );
188
+ const serialized = JSON.stringify(boundary.audit);
189
+ assert.ok(!serialized.includes('access-secret'));
190
+ assert.ok(!serialized.includes('refresh-secret'));
191
+ assert.equal(boundary.audit.personalState.hostConfigRead, false);
192
+ assert.ok(Object.values(boundary.audit.featuresEnabled).every((value) => value === false));
193
+ assert.equal(boundary.audit.authArtifactFilesCopied, 0);
194
+ const runtimeConfig = boundary.loadConfig();
195
+ assert.deepEqual(Object.keys(runtimeConfig.providers), ['openai-oauth']);
196
+ assert.equal(runtimeConfig.providers['openai-oauth'].apiKey, undefined);
197
+ const ephemeralRoot = boundary.rootDir;
198
+ boundary.cleanup();
199
+ assert.equal(existsSync(ephemeralRoot), false);
200
+ boundary = null;
201
+ } finally {
202
+ boundary?.cleanup();
203
+ if (previous.data === undefined) delete process.env.MIXDOG_DATA_DIR;
204
+ else process.env.MIXDOG_DATA_DIR = previous.data;
205
+ if (previous.home === undefined) delete process.env.MIXDOG_HOME;
206
+ else process.env.MIXDOG_HOME = previous.home;
207
+ if (previous.auth === undefined) delete process.env.OPENAI_OAUTH_CREDENTIALS_PATH;
208
+ else process.env.OPENAI_OAUTH_CREDENTIALS_PATH = previous.auth;
209
+ if (previous.behavior === undefined) delete process.env.MIXDOG_PERSONAL_BEHAVIOR;
210
+ else process.env.MIXDOG_PERSONAL_BEHAVIOR = previous.behavior;
211
+ if (previous.debug === undefined) delete process.env.MIXDOG_DEBUG_AGENT;
212
+ else process.env.MIXDOG_DEBUG_AGENT = previous.debug;
213
+ if (previous.anthropic === undefined) delete process.env.ANTHROPIC_API_KEY;
214
+ else process.env.ANTHROPIC_API_KEY = previous.anthropic;
215
+ rmSync(host.root, { recursive: true, force: true });
216
+ }
217
+ });
218
+
219
+ test('verified patch cache seeding copies only a binary matching the bundled manifest', () => {
220
+ const host = fixtureHost();
221
+ const patchDir = join(host.data, 'patch-bin');
222
+ const targetData = join(host.root, 'target-data');
223
+ const manifestPath = join(host.root, 'patch-manifest.json');
224
+ const platform = `${process.platform === 'win32' ? 'win32' : process.platform}-${process.arch}`;
225
+ const version = 'fixture';
226
+ const binaryName = `mixdog-patch-${version}${process.platform === 'win32' ? '.exe' : ''}`;
227
+ const binary = Buffer.from('verified native patch fixture');
228
+ const manifest = {
229
+ version,
230
+ assets: {
231
+ [platform]: {
232
+ url: 'https://example.invalid/mixdog-patch',
233
+ sha256: createHash('sha256').update(binary).digest('hex'),
234
+ },
235
+ },
236
+ };
237
+ const previousData = process.env.MIXDOG_DATA_DIR;
238
+ try {
239
+ mkdirSync(patchDir, { recursive: true });
240
+ writeFileSync(join(patchDir, binaryName), binary);
241
+ writeFileSync(manifestPath, JSON.stringify(manifest));
242
+ assert.equal(
243
+ seedVerifiedPatchBinaryCache(host.data, targetData, { manifestPath }),
244
+ true,
245
+ );
246
+ assert.deepEqual(
247
+ readdirSync(join(targetData, 'patch-bin')).sort(),
248
+ [binaryName, 'manifest.json'].sort(),
249
+ );
250
+ assert.deepEqual(readFileSync(join(targetData, 'patch-bin', binaryName)), binary);
251
+ assert.deepEqual(
252
+ readFileSync(join(targetData, 'patch-bin', 'manifest.json')),
253
+ Buffer.from(JSON.stringify(manifest)),
254
+ );
255
+
256
+ writeFileSync(join(patchDir, binaryName), 'corrupt');
257
+ const corruptTarget = join(host.root, 'corrupt-target-data');
258
+ assert.equal(
259
+ seedVerifiedPatchBinaryCache(host.data, corruptTarget, { manifestPath }),
260
+ false,
261
+ );
262
+ assert.equal(existsSync(join(corruptTarget, 'patch-bin')), false);
263
+ } finally {
264
+ if (previousData === undefined) delete process.env.MIXDOG_DATA_DIR;
265
+ else process.env.MIXDOG_DATA_DIR = previousData;
266
+ rmSync(host.root, { recursive: true, force: true });
267
+ }
268
+ });
269
+
270
+ test('API-key auth is captured only for the selected provider and approved overrides are explicit', () => {
271
+ const previous = {
272
+ openai: process.env.OPENAI_API_KEY,
273
+ anthropic: process.env.ANTHROPIC_API_KEY,
274
+ behavior: process.env.MIXDOG_OUTPUT_STYLE,
275
+ timeout: process.env.MIXDOG_PROVIDER_FIRST_BYTE_TIMEOUT_MS,
276
+ prompt: process.env.MIXDOG_PROMPT,
277
+ workflow: process.env.MIXDOG_WORKFLOW,
278
+ };
279
+ process.env.OPENAI_API_KEY = 'selected-openai-secret';
280
+ process.env.ANTHROPIC_API_KEY = 'unrelated-anthropic-secret';
281
+ process.env.MIXDOG_OUTPUT_STYLE = 'personal-style';
282
+ process.env.MIXDOG_PROVIDER_FIRST_BYTE_TIMEOUT_MS = '12345';
283
+ process.env.MIXDOG_PROMPT = 'host behavioral prompt';
284
+ process.env.MIXDOG_WORKFLOW = 'host behavioral workflow';
285
+ let boundary;
286
+ try {
287
+ boundary = createPristineExecutionBoundary({
288
+ provider: 'openai',
289
+ model: 'gpt-explicit',
290
+ });
291
+ assert.equal(process.env.OPENAI_API_KEY, undefined);
292
+ assert.equal(process.env.ANTHROPIC_API_KEY, undefined);
293
+ assert.equal(process.env.MIXDOG_OUTPUT_STYLE, undefined);
294
+ assert.equal(process.env.MIXDOG_PROVIDER_FIRST_BYTE_TIMEOUT_MS, '12345');
295
+ assert.equal(process.env.MIXDOG_PROMPT, undefined);
296
+ assert.equal(process.env.MIXDOG_WORKFLOW, undefined);
297
+ const runtimeConfig = boundary.loadConfig();
298
+ assert.deepEqual(Object.keys(runtimeConfig.providers), ['openai']);
299
+ assert.equal(runtimeConfig.providers.openai.apiKey, 'selected-openai-secret');
300
+ assert.ok(!JSON.stringify(boundary.audit).includes('selected-openai-secret'));
301
+ assert.throws(
302
+ () => createPristineExecutionBoundary({
303
+ provider: 'openai',
304
+ model: 'gpt-explicit',
305
+ approvedExecutionEnv: { MIXDOG_OUTPUT_STYLE: 'forbidden' },
306
+ }),
307
+ /unapproved pristine execution environment override/,
308
+ );
309
+ } finally {
310
+ boundary?.cleanup();
311
+ if (previous.openai === undefined) delete process.env.OPENAI_API_KEY;
312
+ else process.env.OPENAI_API_KEY = previous.openai;
313
+ if (previous.anthropic === undefined) delete process.env.ANTHROPIC_API_KEY;
314
+ else process.env.ANTHROPIC_API_KEY = previous.anthropic;
315
+ if (previous.behavior === undefined) delete process.env.MIXDOG_OUTPUT_STYLE;
316
+ else process.env.MIXDOG_OUTPUT_STYLE = previous.behavior;
317
+ if (previous.timeout === undefined) delete process.env.MIXDOG_PROVIDER_FIRST_BYTE_TIMEOUT_MS;
318
+ else process.env.MIXDOG_PROVIDER_FIRST_BYTE_TIMEOUT_MS = previous.timeout;
319
+ if (previous.prompt === undefined) delete process.env.MIXDOG_PROMPT;
320
+ else process.env.MIXDOG_PROMPT = previous.prompt;
321
+ if (previous.workflow === undefined) delete process.env.MIXDOG_WORKFLOW;
322
+ else process.env.MIXDOG_WORKFLOW = previous.workflow;
323
+ }
324
+ });
325
+
326
+ test('selected API-key lookup is provider-scoped and xAI aliases are scrubbed after capture', () => {
327
+ const previous = {
328
+ grok: process.env.GROK_API_KEY,
329
+ xai: process.env.XAI_API_KEY,
330
+ openai: process.env.OPENAI_API_KEY,
331
+ };
332
+ process.env.GROK_API_KEY = 'selected-grok-alias-secret';
333
+ delete process.env.XAI_API_KEY;
334
+ process.env.OPENAI_API_KEY = 'unrelated-openai-secret';
335
+ const calls = [];
336
+ let boundary;
337
+ try {
338
+ boundary = createPristineExecutionBoundary({
339
+ provider: 'xai',
340
+ model: 'grok-explicit',
341
+ apiKeyResolver(provider) {
342
+ calls.push(provider);
343
+ return process.env.GROK_API_KEY;
344
+ },
345
+ });
346
+ assert.deepEqual(calls, ['xai']);
347
+ assert.deepEqual(AGENT_PROVIDER_ENV_ALIASES.xai, ['GROK_API_KEY']);
348
+ assert.equal(boundary.loadConfig().providers.xai.apiKey, 'selected-grok-alias-secret');
349
+ assert.equal(process.env.GROK_API_KEY, undefined);
350
+ assert.equal(process.env.XAI_API_KEY, undefined);
351
+ assert.equal(process.env.OPENAI_API_KEY, undefined);
352
+ assert.ok(!JSON.stringify(boundary.audit).includes('selected-grok-alias-secret'));
353
+ } finally {
354
+ boundary?.cleanup();
355
+ if (previous.grok === undefined) delete process.env.GROK_API_KEY;
356
+ else process.env.GROK_API_KEY = previous.grok;
357
+ if (previous.xai === undefined) delete process.env.XAI_API_KEY;
358
+ else process.env.XAI_API_KEY = previous.xai;
359
+ if (previous.openai === undefined) delete process.env.OPENAI_API_KEY;
360
+ else process.env.OPENAI_API_KEY = previous.openai;
361
+ }
362
+ });
363
+
364
+ test('API-key 401 recovery modules cannot reload general behavioral config', () => {
365
+ for (const relative of [
366
+ '../src/runtime/agent/orchestrator/providers/anthropic.mjs',
367
+ '../src/runtime/agent/orchestrator/providers/gemini.mjs',
368
+ '../src/runtime/agent/orchestrator/providers/openai-compat.mjs',
369
+ '../src/runtime/agent/orchestrator/providers/openai-ws.mjs',
370
+ ]) {
371
+ const source = readFileSync(fileURLToPath(new URL(relative, import.meta.url)), 'utf8');
372
+ assert.doesNotMatch(source, /\bloadConfig\b/, relative);
373
+ assert.match(source, /\bgetAgentApiKey\b/, relative);
374
+ }
375
+ });
376
+
377
+ test('runHeadlessRole audits the isolated boundary and never invokes a provider in this test', async () => {
378
+ const host = fixtureHost();
379
+ const previousData = process.env.MIXDOG_DATA_DIR;
380
+ const previousTrace = process.env.MIXDOG_AGENT_TRACE_PATH;
381
+ const tracePath = join(host.root, 'bench-trace', 'agent-trace.jsonl');
382
+ process.env.MIXDOG_DATA_DIR = host.data;
383
+ process.env.MIXDOG_AGENT_TRACE_PATH = tracePath;
384
+ let ephemeralRoot;
385
+ let spawnArgs;
386
+ const errors = [];
387
+ let factoryCalls = 0;
388
+ const signalListenerBaseline = process.listenerCount('SIGTERM');
389
+ let closeKeptSignalHandler = false;
390
+ let boundaryCleanupKeptSignalHandler = false;
391
+ try {
392
+ const code = await runHeadlessRole({
393
+ agent: 'worker',
394
+ message: 'fixture only',
395
+ provider: 'openai-oauth',
396
+ model: 'gpt-explicit',
397
+ effort: 'high',
398
+ fast: true,
399
+ write: () => {},
400
+ writeErr: (text) => errors.push(text),
401
+ agentRunnerFactory: async (_cwd, boundary) => {
402
+ const { appendAgentTrace } = await import('../src/runtime/agent/orchestrator/agent-trace.mjs');
403
+ factoryCalls += 1;
404
+ ephemeralRoot = process.env.MIXDOG_HOME;
405
+ assert.deepEqual(Object.keys(boundary.loadConfig().providers), ['openai-oauth']);
406
+ const originalCleanup = boundary.cleanup;
407
+ boundary.cleanup = () => {
408
+ boundaryCleanupKeptSignalHandler = process.listenerCount('SIGTERM') > signalListenerBaseline;
409
+ originalCleanup();
410
+ };
411
+ return {
412
+ async execute(args) {
413
+ if (args.type === 'spawn') {
414
+ spawnArgs = args;
415
+ appendAgentTrace({
416
+ kind: 'headless-pristine-trace-test',
417
+ session_id: 'sess_headless_pristine_trace',
418
+ });
419
+ return 'fixture completed without provider';
420
+ }
421
+ if (args.type === 'close') {
422
+ closeKeptSignalHandler = process.listenerCount('SIGTERM') > signalListenerBaseline;
423
+ }
424
+ return '';
425
+ },
426
+ };
427
+ },
428
+ });
429
+ assert.equal(code, 0);
430
+ assert.equal(factoryCalls, 1);
431
+ assert.equal(spawnArgs.provider, 'openai-oauth');
432
+ assert.equal(spawnArgs.model, 'gpt-explicit');
433
+ assert.equal(spawnArgs.effort, 'high');
434
+ assert.equal(spawnArgs.fast, true);
435
+ assert.match(errors.join(''), /pristine-execution-audit v1/);
436
+ assert.match(errors.join(''), /personal-files=0/);
437
+ assert.equal(existsSync(ephemeralRoot), false);
438
+ assert.match(readFileSync(tracePath, 'utf8'), /"session_id":"sess_headless_pristine_trace"/);
439
+ assert.equal(closeKeptSignalHandler, true);
440
+ assert.equal(boundaryCleanupKeptSignalHandler, true);
441
+ assert.equal(process.listenerCount('SIGTERM'), signalListenerBaseline);
442
+
443
+ const missing = [];
444
+ const missingCode = await runHeadlessRole({
445
+ agent: 'worker',
446
+ message: 'fixture',
447
+ writeErr: (text) => missing.push(text),
448
+ agentRunnerFactory: async () => {
449
+ throw new Error('must not run');
450
+ },
451
+ });
452
+ assert.equal(missingCode, 1);
453
+ assert.match(missing.join(''), /require both --provider/);
454
+ } finally {
455
+ if (previousData === undefined) delete process.env.MIXDOG_DATA_DIR;
456
+ else process.env.MIXDOG_DATA_DIR = previousData;
457
+ if (previousTrace === undefined) delete process.env.MIXDOG_AGENT_TRACE_PATH;
458
+ else process.env.MIXDOG_AGENT_TRACE_PATH = previousTrace;
459
+ rmSync(host.root, { recursive: true, force: true });
460
+ }
461
+ });
462
+
463
+ function childExit(child, label) {
464
+ return new Promise((resolveExit, reject) => {
465
+ let stderr = '';
466
+ child.stderr.setEncoding('utf8');
467
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
468
+ child.on('error', reject);
469
+ child.on('exit', (code) => resolveExit({ code, stderr, label }));
470
+ });
471
+ }
472
+
473
+ test('headless child signals close the runner and remove the pristine boundary', async () => {
474
+ for (const [signal, expectedCode, hangClose] of [
475
+ ['SIGTERM', 143, true],
476
+ ['SIGHUP', 129, false],
477
+ ]) {
478
+ const host = fixtureHost();
479
+ const readyPath = join(host.root, `${signal}.ready`);
480
+ const rootPath = join(host.root, `${signal}.root`);
481
+ const closedPath = join(host.root, `${signal}.closed`);
482
+ const childSource = `
483
+ import { writeFileSync } from 'node:fs';
484
+ import { runHeadlessRole } from ${JSON.stringify(new URL('../src/headless-role.mjs', import.meta.url).href)};
485
+ await runHeadlessRole({
486
+ agent: 'worker',
487
+ message: 'signal fixture',
488
+ provider: 'openai-oauth',
489
+ model: 'gpt-explicit',
490
+ write: () => {},
491
+ writeErr: () => {},
492
+ agentRunnerFactory: async (_cwd, boundary) => ({
493
+ async execute(args) {
494
+ if (args.type === 'spawn') {
495
+ writeFileSync(process.env.ROOT_PATH, boundary.rootDir);
496
+ writeFileSync(process.env.READY_PATH, 'ready');
497
+ setTimeout(() => process.emit(process.env.TEST_SIGNAL), 20);
498
+ return 'agent task: signal-fixture';
499
+ }
500
+ if (args.type === 'close') {
501
+ writeFileSync(process.env.CLOSED_PATH, 'closed');
502
+ if (process.env.HANG_CLOSE === '1') return new Promise(() => {});
503
+ return '';
504
+ }
505
+ return new Promise(() => {});
506
+ }
507
+ })
508
+ });
509
+ `;
510
+ try {
511
+ const child = spawn(process.execPath, ['--input-type=module', '--eval', childSource], {
512
+ cwd: REPO_ROOT,
513
+ env: {
514
+ ...process.env,
515
+ MIXDOG_DATA_DIR: host.data,
516
+ READY_PATH: readyPath,
517
+ ROOT_PATH: rootPath,
518
+ CLOSED_PATH: closedPath,
519
+ TEST_SIGNAL: signal,
520
+ HANG_CLOSE: hangClose ? '1' : '0',
521
+ },
522
+ stdio: ['ignore', 'ignore', 'pipe'],
523
+ });
524
+ const result = await childExit(child, signal);
525
+ assert.equal(result.code, expectedCode, result.stderr);
526
+ assert.equal(readFileSync(closedPath, 'utf8'), 'closed');
527
+ const ephemeralRoot = readFileSync(rootPath, 'utf8');
528
+ assert.equal(existsSync(ephemeralRoot), false);
529
+ assert.equal(existsSync(readyPath), true);
530
+ } finally {
531
+ rmSync(host.root, { recursive: true, force: true });
532
+ }
533
+ }
534
+ });
535
+
536
+ test('official CLI rejects a missing route without touching the host data tree', () => {
537
+ const host = fixtureHost();
538
+ try {
539
+ const before = execFileSync(
540
+ process.execPath,
541
+ ['--input-type=module', '-e', `
542
+ import { readdirSync, readFileSync } from 'node:fs';
543
+ import { join } from 'node:path';
544
+ const root = process.argv[1];
545
+ const walk = (dir, base = '') => readdirSync(dir, { withFileTypes: true })
546
+ .flatMap((entry) => entry.isDirectory()
547
+ ? walk(join(dir, entry.name), join(base, entry.name))
548
+ : [[join(base, entry.name).replaceAll('\\\\', '/'), readFileSync(join(dir, entry.name), 'utf8')]])
549
+ .sort((a, b) => a[0].localeCompare(b[0]));
550
+ process.stdout.write(JSON.stringify(walk(root)));
551
+ `, host.data],
552
+ { encoding: 'utf8' },
553
+ );
554
+ for (const args of [
555
+ ['worker', 'fixture'],
556
+ ['worker', 'fixture', '--provider', '--model', 'gpt-x'],
557
+ ['--provider', 'openai-oauth', '--model', 'worker', 'fixture'],
558
+ ['--provider', 'openai-oauth', '--model', 'gpt-x', '--effort', 'worker', 'fixture'],
559
+ ['--provider', 'openai-oauth', '--model', 'gpt-x', 'worker', 'fixture', '--bogus'],
560
+ ['--provider', 'openai-oauth', '--model', 'gpt-x', '--workflow', 'default', 'worker', 'fixture'],
561
+ ['--provider', 'openai', '--model', 'gpt', '--bogus', 'worker', 'task'],
562
+ ['--workflow', 'worker', 'task'],
563
+ ]) {
564
+ const result = spawnSync(process.execPath, ['src/cli.mjs', ...args], {
565
+ cwd: REPO_ROOT,
566
+ env: { ...process.env, MIXDOG_HOME: host.root, MIXDOG_DATA_DIR: host.data },
567
+ encoding: 'utf8',
568
+ });
569
+ assert.equal(result.status, 1);
570
+ assert.match(
571
+ result.stderr,
572
+ /require both --provider|requires a non-option value|route value before headless role|unknown option --bogus|--workflow is not supported/,
573
+ );
574
+ }
575
+ const after = execFileSync(
576
+ process.execPath,
577
+ ['--input-type=module', '-e', `
578
+ import { readdirSync, readFileSync } from 'node:fs';
579
+ import { join } from 'node:path';
580
+ const root = process.argv[1];
581
+ const walk = (dir, base = '') => readdirSync(dir, { withFileTypes: true })
582
+ .flatMap((entry) => entry.isDirectory()
583
+ ? walk(join(dir, entry.name), join(base, entry.name))
584
+ : [[join(base, entry.name).replaceAll('\\\\', '/'), readFileSync(join(dir, entry.name), 'utf8')]])
585
+ .sort((a, b) => a[0].localeCompare(b[0]));
586
+ process.stdout.write(JSON.stringify(walk(root)));
587
+ `, host.data],
588
+ { encoding: 'utf8' },
589
+ );
590
+ assert.equal(after, before);
591
+ } finally {
592
+ rmSync(host.root, { recursive: true, force: true });
593
+ }
594
+ });
595
+
596
+ test('Terminal-Bench and Node consume the same pristine contract document', () => {
597
+ assert.equal(PRISTINE_EXECUTION_CONTRACT.schemaVersion, 1);
598
+ assert.deepEqual(
599
+ Object.keys(PRISTINE_EXECUTION_CONTRACT.oauthProviders).sort(),
600
+ ['anthropic-oauth', 'grok-oauth', 'openai-oauth'],
601
+ );
602
+ for (const behavioralName of [
603
+ 'MIXDOG_PROMPT',
604
+ 'MIXDOG_PROVIDER',
605
+ 'MIXDOG_MODEL',
606
+ 'MIXDOG_EFFORT',
607
+ 'MIXDOG_FAST',
608
+ 'MIXDOG_WORKFLOW',
609
+ ]) {
610
+ assert.ok(!PRISTINE_EXECUTION_CONTRACT.approvedExecutionEnv.includes(behavioralName));
611
+ assert.ok(PRISTINE_EXECUTION_CONTRACT.benchmarkRouteEnv.includes(behavioralName));
612
+ }
613
+ assert.ok(PRISTINE_EXECUTION_CONTRACT.approvedExecutionEnv.includes('MIXDOG_AGENT_TRACE_PATH'));
614
+ });
@@ -134,14 +134,23 @@ requireAll(workflow, 'Default approval', [
134
134
  /initial\/additional\/changed requests reset planning/, /scope change needs a revised plan and fresh approval/,
135
135
  /no edits, state mutation, or delegation/,
136
136
  ]);
137
+ const WORKER_HEAVY_ROUTING_CONTRACT = [
138
+ /select worker or heavy worker by solution and validation complexity/,
139
+ /worker owns work whose approach is established and whose correctness can be shown with a focused check/,
140
+ /heavy worker owns work requiring investigation to establish the approach, resolution of material uncertainty or dependencies, iteration across hypotheses or checkpoints, or multi-step, empirical, integration, or real-use validation/,
141
+ ];
142
+ requireAll(workflow, 'Default Worker/Heavy Worker routing', WORKER_HEAVY_ROUTING_CONTRACT);
143
+ assert(
144
+ !/fully\s+specified\s+artifact/i.test(workflow),
145
+ 'Default: removed Worker routing criterion must not return',
146
+ );
137
147
  const ROUTING_REVIEW_POLICY = [
138
- /lead-direct work is allowed only for pure read\/analysis, git\/configuration/,
139
- /user explicitly supplies both the exact target and exact replacement\/output/,
140
- /never infer an exemption from a task name, file count, or perceived difficulty/,
141
- /every other implementation, reverse engineering, debugging application, or artifact generation delegates to the matching agent/,
148
+ /lead-direct execution is limited to a conceptually one-step mechanical action with an immediate deterministic check/,
149
+ /routine configuration\/git work/,
150
+ /operational support for planning, delegation, or verification/,
151
+ /multiple steps must not be reframed as one command to fit this boundary/,
152
+ /work requiring implementation judgment, investigation, iteration, or artifact authoring delegates to worker or heavy worker/,
142
153
  /debugger owns diagnosis and reverse engineering/,
143
- /worker applies an established bounded change or fully specified artifact/,
144
- /heavy worker owns implementation that must establish the change through investigation or staged delivery/,
145
154
  /applying a debugger result is implementation, not diagnosis/,
146
155
  /review is exempt only for pure read\/analysis with no edit, artifact, or state mutation; git\/configuration; or a request where the user explicitly supplies both the exact target and exact replacement\/output\. every non-exempt mutation or artifact/,
147
156
  /every non-exempt mutation or artifact/,
@@ -65,7 +65,7 @@ async function main() {
65
65
  ['Proceed without review', true],
66
66
  ]) assert(reviewSkipViolation(phrase) === expected, `review-skip detector case failed: ${phrase}`);
67
67
  const skipsReview = reviewSkipViolation(workflow);
68
- assert(hasAll(lead, 'workflow permits delegation', 'use `agent`', 'implementation', 'research', 'review', 'debugging'), 'lead rules must conditionally direct scoped work to agents');
68
+ assert(hasAll(lead, 'write-role agents self-verify', 'cross-scope verification', 'benches', 'all git', 'current project/workspace'), 'lead tool rules must preserve verification, git, and workspace ownership');
69
69
  assert(hasAll(workflow, 'after approval', 'delegate', 'by default'), 'default workflow must delegate after approval');
70
70
  assert(hasAll(workflow, 'coordinates', 'git', '1-edit', '1-check'), 'default workflow must limit Lead direct work');
71
71
  assert(hasAll(workflow, 'implementation/research/debugging', 'matching agent'), 'default workflow must route other work to matching agents');