@thebackstoryis/engineering-with-ai 0.3.1-beta.0 → 0.3.1

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 (40) hide show
  1. package/Docs/context-management-and-token-efficiency.md +1 -1
  2. package/Docs/maintainers/contributing.md +8 -0
  3. package/Docs/operations/dashboard-configuration.md +22 -0
  4. package/Docs/operations/installation-updating-and-entitlements.md +40 -2
  5. package/Docs/reference/capabilities-and-project-layout.md +11 -6
  6. package/Docs/reference/cli-and-configuration.md +32 -0
  7. package/README.md +5 -10
  8. package/config/autonomy.schema.json +76 -11
  9. package/config/project.schema.json +29 -1
  10. package/package.json +3 -2
  11. package/public/autonomy.js +10 -4
  12. package/public/coding-providers.js +72 -0
  13. package/public/grok-credentials.js +42 -0
  14. package/public/index.html +36 -1
  15. package/public/styles.css +25 -0
  16. package/skills-src/ewai-context/SKILL.md +1 -0
  17. package/skills-src/ewai-deliver/SKILL.md +3 -1
  18. package/skills-src/ewai-pipeline/SKILL.md +1 -0
  19. package/src/autonomy.mjs +1 -1
  20. package/src/cli.mjs +69 -9
  21. package/src/coding-providers.mjs +81 -0
  22. package/src/companion.mjs +19 -4
  23. package/src/config-document.mjs +49 -0
  24. package/src/dashboard-preferences.mjs +7 -48
  25. package/src/delivery.mjs +8 -2
  26. package/src/discovery.mjs +5 -4
  27. package/src/grok-credentials.mjs +95 -0
  28. package/src/install.mjs +10 -4
  29. package/src/project.mjs +10 -2
  30. package/src/runtime/afk-conductor.mjs +84 -18
  31. package/src/runtime/autonomy-supervisor.mjs +23 -5
  32. package/src/runtime/autonomy-workers.mjs +11 -2
  33. package/src/runtime/dashboard-actions.mjs +1 -1
  34. package/src/runtime/dashboard-server.mjs +29 -0
  35. package/src/runtime/grok-provider.mjs +269 -0
  36. package/src/runtime/mcp-config.mjs +34 -2
  37. package/src/runtime/mcp-server.mjs +6 -6
  38. package/src/runtime/provider-adapters.mjs +207 -31
  39. package/src/validation-config.mjs +18 -7
  40. package/templates/SPECS/pipeline.yaml +3 -0
@@ -1,12 +1,17 @@
1
- import { accessSync, constants, createWriteStream, mkdirSync, existsSync, realpathSync, lstatSync, openSync, readSync, closeSync, fstatSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
- import { delimiter, isAbsolute, resolve } from 'node:path';
1
+ import { accessSync, constants, createWriteStream, mkdirSync, existsSync, realpathSync, lstatSync, openSync, readSync, readFileSync, closeSync, fstatSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { delimiter, dirname, isAbsolute, resolve } from 'node:path';
3
3
  import { spawn, spawnSync } from 'node:child_process';
4
4
  import { createHash } from 'node:crypto';
5
5
  import { release, tmpdir } from 'node:os';
6
6
  import { createServer } from 'node:http';
7
7
  import { CONTEXT_ESTIMATE_METHOD, estimateContextTokens } from './context-assembly.mjs';
8
+ import {modelGuidance, readCodingPolicy, assertCodingPolicyFresh} from '../coding-providers.mjs';
9
+ import {prepareGrokRuntime, parseGrokWorkerResult, prepareGrokTaskSnapshot, acceptGrokTaskSnapshot, assertGrokTaskEvidenceFresh} from './grok-provider.mjs';
10
+ import {resolveGrokApiKey} from '../grok-credentials.mjs';
8
11
 
9
12
  export const AFK_PROVIDERS = Object.freeze(['codex', 'claude', 'antigravity']);
13
+ export const SUPPORTED_AFK_PROVIDERS = Object.freeze([...AFK_PROVIDERS,'grok']);
14
+ const grokInvocations = new WeakMap();
10
15
  // Retain owned child handles after an uncertain stop; do not turn a missing
11
16
  // close event into success or lose the ability to observe a later exit.
12
17
  const unsettledProcesses = new Set();
@@ -60,6 +65,15 @@ export function commandAvailable(command, env = process.env) {
60
65
  });
61
66
  }
62
67
 
68
+ export function captureProviderIdentity(provider) {
69
+ if(!SUPPORTED_AFK_PROVIDERS.includes(provider))throw new Error('Provider identity is unavailable.');
70
+ const executable=installedExecutable(providerCommand(provider));
71
+ const version=spawnSync(executable,['--version'],{env:phaseEnv(),encoding:'utf8',timeout:5000,maxBuffer:1024});
72
+ if(version.status!==0||version.error)throw new Error('Provider version is unavailable.');
73
+ return {provider,identityDigest:'sha256:'+createHash('sha256').update(JSON.stringify([
74
+ executableDigest(executable),version.stdout.trim(),release(),process.arch])).digest('hex')};
75
+ }
76
+
63
77
  function claudeTools(task, mode) {
64
78
  if (mode === 'review') return ['Read', 'Glob', 'Grep'];
65
79
  const commands = (task?.allowed_commands ?? []).map((command) => `Bash(${command})`);
@@ -67,7 +81,7 @@ function claudeTools(task, mode) {
67
81
  }
68
82
 
69
83
  export function buildProviderInvocation(provider, input = {}) {
70
- if (!AFK_PROVIDERS.includes(provider)) throw new Error(`Unsupported AFK provider: ${provider}`);
84
+ if (!SUPPORTED_AFK_PROVIDERS.includes(provider)) throw new Error(`Unsupported AFK provider: ${provider}`);
71
85
  assertAfkMode(input.mode);
72
86
  const mode = input.mode === 'review' ? 'review' : 'implementation';
73
87
  const cwd = resolve(input.cwd);
@@ -76,16 +90,26 @@ export function buildProviderInvocation(provider, input = {}) {
76
90
  provider,
77
91
  command: providerCommand(provider),
78
92
  cwd,
79
- prompt: String(input.prompt ?? ''),
93
+ prompt: String(input.prompt ?? '')+modelGuidance(provider,input.codingPolicy),
80
94
  timeoutMs: Number(input.timeoutMs ?? 45 * 60 * 1000),
81
95
  mode,
82
96
  task: input.task ?? null,
83
- promptMeasurement: measureProviderPrompt(input.prompt ?? ''),
97
+ promptMeasurement: measureProviderPrompt(String(input.prompt ?? '')+modelGuidance(provider,input.codingPolicy)),
84
98
  providerUsage: normaliseProviderUsage(provider, input.providerUsage),
85
99
  args: [],
86
100
  };
87
101
 
88
- if (provider === 'codex') {
102
+ if (provider === 'grok') {
103
+ if(model)throw new Error('Grok workers retain native model selection. Use an advisory model suggestion.');
104
+ assertGrokTaskEvidenceFresh(input.grokEvidence,mode,{...input,prompt:invocation.prompt});
105
+ const capability=prepareGrokBuildProvider(mode);
106
+ if(capability.status!=='requires-conformance')throw new Error('Grok coding/review mode is unavailable: '+capability.code);
107
+ const policyRoot=input.policyRoot??cwd,policyDigest=input.policyDigest??readCodingPolicy(policyRoot).policyDigest;
108
+ grokInvocations.set(invocation,{handle:capability,policyRoot,policyDigest,evidence:input.grokEvidence,mode,
109
+ input:{cwd,prompt:invocation.prompt,task:structuredClone(input.task),timeoutMs:invocation.timeoutMs}});
110
+ invocation.identityDigest=capability.identityDigest;
111
+ invocation.grokEvidence=input.grokEvidence;
112
+ } else if (provider === 'codex') {
89
113
  invocation.args = ['--ask-for-approval', 'never', 'exec', '--color', 'never', '--sandbox', mode === 'review' ? 'read-only' : 'workspace-write', '-C', cwd];
90
114
  if (mode === 'implementation') invocation.args.splice(3, 0, '--json');
91
115
  if (model) invocation.args.push('--model', model);
@@ -121,6 +145,7 @@ export function invokeProvider(invocation, options = {}) {
121
145
  if (options.signal?.aborted) return Promise.resolve({ provider: invocation.provider, exitCode: -1,
122
146
  signal: '', cancelled: true, timedOut: false, executionStopped: true, processGroupStopped: true,
123
147
  durationMs: 0, output: '', logPath: invocation.logPath, providerUsage: null });
148
+ if(invocation.provider==='grok')return invokeGrokBuild(invocation,options);
124
149
  mkdirSync(resolve(invocation.logPath, '..'), { recursive: true });
125
150
  const output = createWriteStream(invocation.logPath, { flags: 'a' });
126
151
  const startedAt = Date.now();
@@ -220,7 +245,7 @@ function assertAfkMode(mode) {
220
245
  // serialisable worker/browser supplied executable paths or permission flags.
221
246
  const phaseCapabilities = new WeakMap();
222
247
  const unavailablePhase = code => ({ status: 'unavailable', code, requiresHuman: true });
223
- const phaseVersions = Object.freeze({ claude: '2.1.257 (Claude Code)', sandbox: 'macos-seatbelt-phase-v1' });
248
+ const phaseVersions = Object.freeze({ claude: '2.1.257 (Claude Code)', grok: 'grok 1.0.44 (5b807183dd79) [stable]', sandbox: 'macos-seatbelt-phase-v1' });
224
249
  const phaseEnv = () => ({ PATH: process.env.PATH ?? '', LANG: 'en_US.UTF-8' });
225
250
 
226
251
  function installedExecutable(command) {
@@ -249,41 +274,151 @@ function executableDigest(path) {
249
274
  export function preparePhaseProvider(provider) {
250
275
  // Additional providers/platforms stay unavailable until the same executable
251
276
  // and configuration-isolation conformance has actually been demonstrated.
252
- if (provider !== 'claude' || process.platform !== 'darwin') return unavailablePhase('phase-provider-mode-unavailable');
277
+ if (!['claude', 'grok'].includes(provider) || process.platform !== 'darwin') return unavailablePhase('phase-provider-mode-unavailable');
253
278
  try {
254
- const executable = installedExecutable('claude'), sandbox = realpathSync('/usr/bin/sandbox-exec');
279
+ if (provider === 'grok' && ['managed_config.toml', 'requirements.toml'].some(name => existsSync(resolve('/etc/grok', name)))) return unavailablePhase('phase-provider-system-config-unverified');
280
+ const executable = installedExecutable(provider), sandbox = realpathSync('/usr/bin/sandbox-exec');
255
281
  const version = path => {
256
282
  const result = spawnSync(path, ['--version'], { env: phaseEnv(), encoding: 'utf8', timeout: 5000, maxBuffer: 1024 });
257
283
  if (result.status !== 0 || result.error) throw new Error('phase-provider-version-unavailable');
258
284
  return result.stdout.trim();
259
285
  };
260
- if (version(executable) !== phaseVersions.claude) return unavailablePhase('phase-provider-version-unverified');
286
+ if (version(executable) !== phaseVersions[provider]) return unavailablePhase('phase-provider-version-unverified');
261
287
  const hashes = [executableDigest(executable), executableDigest(sandbox)];
262
- const handle = Object.freeze({ status: 'requires-conformance', provider, version: phaseVersions.claude,
288
+ const handle = Object.freeze({ status: 'requires-conformance', provider, version: phaseVersions[provider],
263
289
  sandboxVersion: phaseVersions.sandbox,
264
290
  identityDigest: createHash('sha256').update(JSON.stringify([hashes, release(), process.arch, phaseVersions.sandbox])).digest('hex') });
265
- phaseCapabilities.set(handle, { executable, sandbox, hashes, verified: false });
291
+ phaseCapabilities.set(handle, { provider, mode:'phase', executable, sandbox, hashes, verified: false });
266
292
  return handle;
267
293
  } catch { return unavailablePhase('phase-provider-identity-unavailable'); }
268
294
  }
269
295
 
296
+ export function prepareGrokBuildProvider(mode) {
297
+ if(!['implementation','review'].includes(mode))return unavailablePhase('phase-provider-mode-unavailable');
298
+ const phase=preparePhaseProvider('grok');if(phase.status!=='requires-conformance')return phase;
299
+ const handle=Object.freeze({...phase,mode,identityDigest:createHash('sha256').update(phase.identityDigest+':grok-task-snapshot-v1:'+mode).digest('hex')});
300
+ phaseCapabilities.set(handle,{...phaseCapabilities.get(phase),mode});return handle;
301
+ }
302
+
303
+ async function invokeGrokBuild(invocation,options) {
304
+ const captured=grokInvocations.get(invocation),startedAt=Date.now();
305
+ if(!captured)throw new Error('Grok invocation must be prepared by the host.');
306
+ const failure=(code,stopped=true)=>({provider:'grok',exitCode:-1,status:'unavailable',code,
307
+ executionStopped:stopped,output:'',logPath:invocation.logPath,durationMs:Date.now()-startedAt});
308
+ const fresh=()=>{if(options.signal?.aborted)throw new Error('phase-cancelled');
309
+ currentPhaseCapability(captured.handle);assertCodingPolicyFresh(captured.policyDigest,readCodingPolicy(captured.policyRoot).policyDigest);
310
+ assertGrokTaskEvidenceFresh(captured.evidence,captured.mode,{...captured.input,policyRoot:captured.policyRoot});
311
+ options.beforeAccept?.();};
312
+ try {
313
+ assertGrokTaskEvidenceFresh(captured.evidence,captured.mode,{...captured.input,policyRoot:captured.policyRoot});
314
+ const proof=await verifyGrokBuildProviderConformance(captured.handle,{signal:options.signal,timeoutMs:Math.min(60000,captured.input.timeoutMs)});
315
+ if(proof.status!=='verified')return failure(proof.code,proof.executionStopped!==false);
316
+ const grokApiKey=resolveGrokApiKey();
317
+ if(!grokApiKey)return failure('phase-provider-credential-unavailable');
318
+ fresh();
319
+ const capability=currentPhaseCapability(captured.handle);
320
+ const result=await captureRestrictedProcess(capability,{...captured.input,grokApiKey,signal:options.signal,maxBytes:1024*1024,beforeAccept:fresh});
321
+ if(result.status!=='complete')return {...failure('phase-provider-'+result.status,result.executionStopped),status:result.status,
322
+ cancelled:result.status==='cancelled',timedOut:result.status==='timed-out'};
323
+ fresh();const parsed=parseGrokWorkerResult(result.output,capability.mode);
324
+ mkdirSync(resolve(invocation.logPath,'..'),{recursive:true});writeFileSync(invocation.logPath,parsed.output,{mode:0o600});
325
+ return {provider:'grok',...parsed,exitCode:parsed.status==='complete'?0:-1,executionStopped:true,
326
+ changedFiles:result.changedFiles??[],promptMeasurement:invocation.promptMeasurement,
327
+ logPath:invocation.logPath,durationMs:Date.now()-startedAt};
328
+ }catch(error){return failure(error.code?.startsWith('grok-credential-')?error.code:error.message.startsWith('grok-task-evidence-')||['phase-cancelled','phase-provider-identity-changed'].includes(error.message)?error.message:'phase-provider-acceptance-failed');}
329
+ }
330
+
331
+ export async function verifyGrokBuildProviderConformance(handle, control={}) {
332
+ let capability;
333
+ try{capability=currentPhaseCapability(handle,false);}catch{return unavailablePhase('phase-provider-conformance-required');}
334
+ if(capability.provider!=='grok'||!['implementation','review'].includes(capability.mode))return unavailablePhase('phase-provider-mode-unavailable');
335
+ capability.verified=false;
336
+ const phaseHandle=Object.freeze({...handle,mode:'phase'});
337
+ phaseCapabilities.set(phaseHandle,{...capability,mode:'phase',verified:false});
338
+ const isolation=await verifyPhaseProviderConformance(phaseHandle,control);
339
+ if(isolation.status!=='verified')return isolation;
340
+ const root=realpathSync(mkdtempSync(resolve(tmpdir(),'ewai-grok-build-conformance-'))),project=resolve(root,'project');
341
+ mkdirSync(project);writeFileSync(resolve(project,'code.txt'),'before');
342
+ const outside=resolve(root,'canonical-approval.json'),requests=[];
343
+ let attack=false,executionStopped=true;
344
+ const server=createServer((request,response)=>{
345
+ if(request.method==='GET'){requests.push({health:['/','/v1','/v1/models'].includes(request.url)});
346
+ response.writeHead(200,{'content-type':'application/json'});response.end(JSON.stringify({data:[{id:'fixture',object:'model'}]}));return;}
347
+ let body='';request.on('data',chunk=>{body+=chunk;if(Buffer.byteLength(body)>2*1024*1024)request.destroy();});
348
+ request.on('end',()=>{
349
+ let value;try{value=JSON.parse(body);}catch{response.writeHead(400);response.end();return;}
350
+ const tools=(value.tools??[]).map(tool=>tool.function?.name??tool.name);
351
+ requests.push({health:false,route:request.url,tools,canary:body.includes('EWAI_UNTRUSTED_INSTRUCTIONS_CANARY'),
352
+ readObserved:!attack&&(value.messages??[]).some(message=>message.role==='tool'&&JSON.stringify(message.content).includes('before'))});
353
+ const title=tools.length===1&&tools[0]==='session_title';
354
+ const finished=(value.messages??[]).some(message=>message.role==='tool');
355
+ const call=!title&&!finished&&tools.length>1;
356
+ const read=capability.mode==='review'&&!attack;
357
+ const delta=call?{tool_calls:[{index:0,id:'fixture-tool',type:'function',function:{name:read?'read_file':'write',
358
+ arguments:JSON.stringify(read?{target_file:'code.txt'}:{file_path:attack?outside:'code.txt',content:'after'})}}]}:{role:'assistant',content:'EWAI_BUILD_FIXTURE_ONLY'};
359
+ response.writeHead(200,{'content-type':'text/event-stream'});
360
+ const event=data=>response.write('data: '+JSON.stringify(data)+'\n\n');
361
+ event({id:'fixture',object:'chat.completion.chunk',created:1,model:'fixture',choices:[{index:0,delta,finish_reason:null}]});
362
+ event({id:'fixture',object:'chat.completion.chunk',created:1,model:'fixture',choices:[{index:0,delta:{},finish_reason:call?'tool_calls':'stop'}],usage:{prompt_tokens:1,completion_tokens:1,total_tokens:2}});
363
+ response.end('data: [DONE]\n\n');
364
+ });
365
+ });
366
+ try{
367
+ await new Promise((ok,no)=>{server.once('error',no);server.listen(0,'127.0.0.1',ok);});
368
+ const fixture={url:'http://127.0.0.1:'+server.address().port,isolationProbe:true,
369
+ taskInput:{cwd:project,task:{read_set:['code.txt'],write_set:['code.txt']}}};
370
+ const input={prompt:'Use only declared task files. Return the fixture token.',timeoutMs:15000,maxBytes:256*1024,signal:control.signal};
371
+ const normal=await captureRestrictedProcess(capability,input,fixture);executionStopped&&=normal.executionStopped;
372
+ const after=readFileSync(resolve(project,'code.txt'),'utf8');
373
+ attack=true;
374
+ const forged=await captureRestrictedProcess(capability,input,fixture);executionStopped&&=forged.executionStopped;
375
+ const expected=capability.mode==='implementation'?['read_file','search_replace','list_dir','grep','write']:['read_file','list_dir','grep'];
376
+ const checks={
377
+ ...Object.fromEntries(['canonicalWritePrevented','outputBoundEnforced','timeoutEnforced','networkIsolated','childProcessPrevented'].map(key=>[key,isolation[key]===true])),
378
+ nativeResult:normal.status==='complete'&&parseGrokWorkerResult(normal.output,capability.mode).output==='EWAI_BUILD_FIXTURE_ONLY',
379
+ declaredWriteBoundary:capability.mode==='implementation'?after==='after'&&normal.changedFiles?.join(',')==='code.txt':after==='before'&&!normal.changedFiles?.length,
380
+ nativeReadBoundary:capability.mode!=='review'||requests.some(request=>request.readObserved),
381
+ inheritedCapabilitiesBlocked:requests.every(r=>r.health===true||r.route==='/v1/chat/completions'&&!r.canary
382
+ &&(r.tools.length===1&&r.tools[0]==='session_title'||r.tools.length===0||r.tools.length===expected.length&&r.tools.every(tool=>expected.includes(tool)))),
383
+ forbiddenWritePrevented:forged.executionStopped&&!existsSync(outside)&&readFileSync(resolve(project,'code.txt'),'utf8')===after,
384
+ executionStopped,
385
+ };
386
+ capability.verified=Object.values(checks).every(value=>value===true);currentPhaseCapability(handle,false);
387
+ return capability.verified?{status:'verified',provider:'grok',mode:capability.mode,identityDigest:handle.identityDigest,
388
+ checks,fixtureServiceOnly:true,authority:'none'}:{...unavailablePhase('phase-provider-build-conformance-failed'),checks,
389
+ requests:requests.map(r=>({route:r.health?'fixture-health':'fixture-model',tools:r.tools??[],inheritedInstructions:r.canary===true}))};
390
+ }catch(error){return {...unavailablePhase('phase-provider-build-conformance-failed'),executionStopped,
391
+ diagnosticCode:/^grok-task-[a-z-]+$/.test(error.message)?error.message:'native-fixture-failed'};}
392
+ finally{server.closeAllConnections();await new Promise(ok=>server.close(ok));if(executionStopped)rmSync(root,{recursive:true,force:true});}
393
+ }
394
+
270
395
  function currentPhaseCapability(handle, requireVerified = true) {
271
396
  const capability = phaseCapabilities.get(handle);
272
397
  if (!capability || requireVerified && !capability.verified) throw new Error('phase-provider-conformance-required');
398
+ if (installedExecutable(capability.provider) !== capability.executable
399
+ || capability.provider === 'grok' && ['managed_config.toml', 'requirements.toml'].some(name => existsSync(resolve('/etc/grok', name)))) {
400
+ capability.verified = false; throw new Error('phase-provider-identity-changed');
401
+ }
273
402
  if (executableDigest(capability.executable) !== capability.hashes[0] || executableDigest(capability.sandbox) !== capability.hashes[1]) {
274
403
  capability.verified = false; throw new Error('phase-provider-identity-changed');
275
404
  }
276
405
  return capability;
277
406
  }
278
407
 
279
- function restrictedPhaseArgs(runtime, command, args, fixture) {
408
+ function restrictedPhaseArgs(runtime, command, args, fixture, grok=null, mode='phase') {
280
409
  // One kernel policy covers every restriction; nested Seatbelt initialisation
281
410
  // is not supported. Denying fork also denies posix_spawn, so a child cannot
282
411
  // detach into a new session and outlive the captured process.
283
412
  const port = fixture?.url ? Number(new URL(fixture.url).port) : null;
413
+ const ancestorDirectories=[];
414
+ for(const start of [runtime,realpathSync(command)]) for(let path=dirname(start);path!==dirname(path);path=dirname(path))ancestorDirectories.push(path);
284
415
  const policy = `(version 1)(allow default)(deny file-write*)`
285
416
  + `(allow file-write* (subpath ${JSON.stringify(runtime)}))(deny process-fork)`
286
- + (fixture ? `(deny network*)${port ? `(allow network-outbound (remote ip "localhost:${port}"))` : ''}` : '');
417
+ + (fixture ? `(deny network*)${port ? `(allow network-outbound (remote ip "localhost:${port}"))` : ''}` : '')
418
+ + (grok ? `(deny file-read-data)(allow file-read-data (literal "/") ${ancestorDirectories.map(path=>`(literal ${JSON.stringify(path)})`).join(' ')} (subpath "/System") (subpath "/usr") (subpath "/Library") (subpath "/dev") (subpath "/opt/homebrew/Cellar") (subpath "/opt/homebrew/lib") (subpath "/opt/homebrew/opt") (literal ${JSON.stringify(realpathSync(command))}) (subpath ${JSON.stringify(runtime)}))`
419
+ + `(deny file-write* (literal ${JSON.stringify(resolve(grok.home,'.grok/config.toml'))}) (literal ${JSON.stringify(resolve(grok.home,'.grok/managed_config.toml'))}) (literal ${JSON.stringify(resolve(grok.home,'.grok/requirements.toml'))}))`
420
+ + ['.grok','.claude','.cursor','.git','.ewai-pipeline'].map(name=>`(deny file-write* (subpath ${JSON.stringify(resolve(grok.cwd,name))}))`).join('')
421
+ + (mode==='review'?`(deny file-write* (subpath ${JSON.stringify(grok.cwd)}))`:'') : '');
287
422
  return ['-p', policy, command, ...args];
288
423
  }
289
424
 
@@ -298,6 +433,8 @@ async function captureRestrictedProcess(capability, input, fixture = null) {
298
433
  if (input.signal?.aborted) return cancelledBeforeSpawn();
299
434
  const directory = realpathSync(mkdtempSync(resolve(tmpdir(), 'ewai-phase-process-')));
300
435
  const identity = lstatSync(directory), runtime = resolve(directory, 'runtime');
436
+ let stopped = true;
437
+ try {
301
438
  mkdirSync(runtime, { mode: 0o700 });
302
439
  if (fixture?.isolationProbe) {
303
440
  // Deliberately hostile *fixture* configuration. A loaded hook/MCP server
@@ -309,16 +446,20 @@ async function captureRestrictedProcess(capability, input, fixture = null) {
309
446
  writeFileSync(resolve(directory, '.mcp.json'), JSON.stringify({ mcpServers: { forbidden: { type: 'http', url: `${fixture.url}/forbidden-mcp` } } }), { mode: 0o600 });
310
447
  writeFileSync(resolve(directory, 'CLAUDE.md'), 'EWAI_UNTRUSTED_INSTRUCTIONS_CANARY', { mode: 0o600 });
311
448
  }
312
- const env = { ...phaseEnv(), CLAUDE_CONFIG_DIR: runtime, CLAUDE_CODE_TMPDIR: runtime, TMPDIR: runtime,
449
+ const mode=capability.mode??'phase';
450
+ const grok = capability.provider === 'grok' ? prepareGrokRuntime(directory, runtime, input.prompt, fixture, mode, {...process.env,XAI_API_KEY:input.grokApiKey}) : null;
451
+ const taskInput=fixture?.taskInput??input;
452
+ const snapshot=grok&&mode!=='phase'&&!fixture?.command
453
+ ?prepareGrokTaskSnapshot(taskInput.cwd,grok.cwd,taskInput.task):null;
454
+ const env = grok?.env ?? { ...phaseEnv(), CLAUDE_CONFIG_DIR: runtime, CLAUDE_CODE_TMPDIR: runtime, TMPDIR: runtime,
313
455
  CLAUDE_CODE_SKIP_PROMPT_HISTORY: '1', CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
314
456
  DISABLE_TELEMETRY: '1', DISABLE_ERROR_REPORTING: '1',
315
457
  ANTHROPIC_API_KEY: fixture ? 'ewai-fixture-not-a-real-key' : process.env.ANTHROPIC_API_KEY };
316
458
  if (fixture?.url) env.ANTHROPIC_BASE_URL = fixture.url;
317
- const command = fixture?.command ?? capability.executable, args = fixture?.args ?? restrictedClaudeArgs();
459
+ if (grok && fixture?.command === process.execPath) env.OPENSSL_CONF = '/dev/null';
460
+ const command = fixture?.command ?? capability.executable, args = fixture?.args ?? grok?.args ?? restrictedClaudeArgs();
318
461
  const launchCommand = capability.sandbox;
319
- const launchArgs = restrictedPhaseArgs(runtime, command, args, fixture);
320
- let stopped = false;
321
- try {
462
+ const launchArgs = restrictedPhaseArgs(runtime, command, args, fixture, grok, mode);
322
463
  const result = await new Promise(resolvePromise => {
323
464
  let child, timer, killTimer, stopDeadline, terminal = null, bytes = 0, output = [], settled = false;
324
465
  const kill = signal => { if (child?.pid) try { process.kill(-child.pid, signal); } catch {} };
@@ -359,7 +500,7 @@ async function captureRestrictedProcess(capability, input, fixture = null) {
359
500
  output: !terminal && stopped ? Buffer.concat(output).toString('utf8') : '', bytes });
360
501
  };
361
502
  if (input.signal?.aborted) { stopped = true; resolvePromise(cancelledBeforeSpawn()); return; }
362
- try { child = spawn(launchCommand, launchArgs, { cwd: directory, env, detached: true, shell: false, stdio: ['pipe', 'pipe', 'pipe'] }); }
503
+ try { stopped=false; child = spawn(launchCommand, launchArgs, { cwd: grok?.cwd ?? directory, env, detached: true, shell: false, stdio: ['pipe', 'pipe', 'pipe'] }); }
363
504
  catch { finish(-1, null); return; }
364
505
  const capture = (chunk, stdout) => {
365
506
  bytes += chunk.length;
@@ -372,8 +513,11 @@ async function captureRestrictedProcess(capability, input, fixture = null) {
372
513
  timer = setTimeout(() => stop('timed-out'), input.timeoutMs);
373
514
  input.signal?.addEventListener('abort', cancel, { once: true });
374
515
  if (input.signal?.aborted) cancel();
375
- child.stdin.end(terminal ? '' : input.prompt);
516
+ child.stdin.end(terminal || grok ? '' : input.prompt);
376
517
  });
518
+ if(snapshot&&result.status==='complete'&&parseGrokWorkerResult(result.output,mode).status==='complete'){
519
+ result.changedFiles=acceptGrokTaskSnapshot(snapshot,{readOnly:mode==='review',beforeAccept:input.beforeAccept});
520
+ }
377
521
  return result;
378
522
  } finally {
379
523
  if (stopped && existsSync(directory)) {
@@ -389,9 +533,15 @@ export async function invokeRestrictedPhaseProvider(handle, input = {}) {
389
533
  || input.signal !== undefined && !(input.signal instanceof AbortSignal)) return unavailablePhase('phase-provider-input-invalid');
390
534
  let capability;
391
535
  try { capability = currentPhaseCapability(handle); } catch { return unavailablePhase('phase-provider-conformance-required'); }
392
- if (!process.env.ANTHROPIC_API_KEY) return unavailablePhase('phase-provider-credential-unavailable');
393
- const captured = await captureRestrictedProcess(capability, { ...input, maxBytes: 256 * 1024 });
536
+ if(capability.mode!=='phase')return unavailablePhase('phase-provider-mode-unavailable');
537
+ if (input.signal?.aborted) return { status: 'cancelled', exitCode: -1, executionStopped: true, spawned: false, output: '' };
538
+ let apiKey;
539
+ try{apiKey=capability.provider==='grok'?resolveGrokApiKey():process.env.ANTHROPIC_API_KEY;}catch{return unavailablePhase('phase-provider-credential-unavailable');}
540
+ if(!apiKey)return unavailablePhase('phase-provider-credential-unavailable');
541
+ const captured = await captureRestrictedProcess(capability, { ...input, grokApiKey:capability.provider==='grok'?apiKey:undefined, maxBytes: 256 * 1024 });
394
542
  if (captured.status !== 'complete') return { ...captured, output: '' };
543
+ try { currentPhaseCapability(handle); } catch { return { ...unavailablePhase('phase-provider-identity-changed'), executionStopped: true, output: '' }; }
544
+ if (capability.provider === 'grok') return { ...parseGrokWorkerResult(captured.output), exitCode: captured.exitCode, executionStopped: true };
395
545
  let envelope;
396
546
  try { envelope = JSON.parse(captured.output); } catch { return { status: 'malformed', executionStopped: true, exitCode: 0, output: '' }; }
397
547
  if (envelope?.type !== 'result' || envelope.subtype !== 'success' || envelope.is_error !== false || typeof envelope.result !== 'string') {
@@ -429,6 +579,10 @@ export async function verifyPhaseProviderConformance(handle, control = {}) {
429
579
  let forcedTool = false;
430
580
  const server = createServer((request, response) => {
431
581
  if (request.method === 'GET' && request.url === '/ewai-network-probe') { response.writeHead(204); response.end(); return; }
582
+ if (capability.provider === 'grok' && request.method === 'GET' && ['/', '/v1', '/v1/models'].includes(request.url)) {
583
+ requests.push({ path: request.url, healthProbe: true }); response.writeHead(200, { 'content-type': 'application/json' });
584
+ response.end(JSON.stringify({ data: [{ id: 'fixture', object: 'model', owned_by: 'ewai' }] })); return;
585
+ }
432
586
  if (request.method === 'HEAD' && request.url === '/api/hello') {
433
587
  requests.push({ path: '/api/hello', healthProbe: true }); response.writeHead(204); response.end(); return;
434
588
  }
@@ -436,9 +590,24 @@ export async function verifyPhaseProviderConformance(handle, control = {}) {
436
590
  request.on('data', chunk => { bytes += chunk.length; if (bytes > 2 * 1024 * 1024) request.destroy(); else body += chunk; });
437
591
  request.on('end', () => {
438
592
  let input; try { input = JSON.parse(body); } catch { requests.push({ path: request.url }); response.writeHead(400); response.end(); return; }
439
- requests.push({ path: request.url, tools: (input.tools ?? []).map(tool => tool.name),
593
+ requests.push({ path: request.url, tools: (input.tools ?? []).map(tool => tool.function?.name ?? tool.name),
594
+ toolFields: (input.tools ?? []).map(tool => Object.keys((tool.function ?? tool).parameters?.properties ?? {})),
595
+ titleOnly: capability.provider === 'grok' && input.tools?.length === 1
596
+ && input.tools[0].function?.name === 'session_title'
597
+ && Object.keys(input.tools[0].function.parameters?.properties ?? {}).join(',') === 'session_title'
598
+ && input.tools[0].function.parameters.properties.session_title.type === 'string',
440
599
  inheritedInstructions: body.includes('EWAI_UNTRUSTED_INSTRUCTIONS_CANARY') });
441
600
  response.writeHead(200, { 'content-type': 'text/event-stream' });
601
+ if (capability.provider === 'grok') {
602
+ const event = value => response.write('data: ' + JSON.stringify(value) + '\n\n');
603
+ const delta = forcedTool ? { tool_calls: [{ index: 0, id: 'ewai-forged', type: 'function', function: {
604
+ name: 'write_file', arguments: JSON.stringify({ file_path: target, content: 'forged approval' }) } }] }
605
+ : { role: 'assistant', content: 'EWAI_RESTRICTED_FIXTURE_ONLY' };
606
+ event({ id: 'ewai-fixture', object: 'chat.completion.chunk', created: 1, model: 'fixture', choices: [{ index: 0, delta, finish_reason: null }] });
607
+ event({ id: 'ewai-fixture', object: 'chat.completion.chunk', created: 1, model: 'fixture', choices: [{ index: 0, delta: {}, finish_reason: forcedTool ? 'tool_calls' : 'stop' }],
608
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } });
609
+ response.end('data: [DONE]\n\n'); return;
610
+ }
442
611
  const event = (type, value) => response.write(`event: ${type}\ndata: ${JSON.stringify({ type, ...value })}\n\n`);
443
612
  event('message_start', { message: { id: 'msg_ewai_fixture', type: 'message', role: 'assistant', model: 'fixture-model',
444
613
  content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 1, output_tokens: 0 } } });
@@ -491,24 +660,31 @@ export async function verifyPhaseProviderConformance(handle, control = {}) {
491
660
  const forged = await capture(capability, { prompt: 'No tools or approvals are authorised.', timeoutMs: 15000, maxBytes: 16384 },
492
661
  { url: fixtureUrl, isolationProbe: true });
493
662
  let refused; try { refused = JSON.parse(forged.output); } catch {}
663
+ const nativeGrok = capability.provider === 'grok';
664
+ const modelRoute = request => nativeGrok ? request.path === '/v1/chat/completions' : request.path.startsWith('/v1/messages');
494
665
  const checks = {
495
666
  ...primitiveChecks,
496
- structuredDraft: model.status === 'complete' && envelope?.type === 'result' && envelope.subtype === 'success' && envelope.is_error === false
667
+ structuredDraft: nativeGrok ? model.status === 'complete' && parseGrokWorkerResult(model.output).output === 'EWAI_RESTRICTED_FIXTURE_ONLY'
668
+ : model.status === 'complete' && envelope?.type === 'result' && envelope.subtype === 'success' && envelope.is_error === false
497
669
  && envelope.result === 'EWAI_RESTRICTED_FIXTURE_ONLY',
498
- inheritedCapabilitiesBlocked: requests.filter(request => request.path.startsWith('/v1/messages')).length === 2
670
+ inheritedCapabilitiesBlocked: (nativeGrok ? requests.filter(modelRoute).length >= 2 : requests.filter(modelRoute).length === 2)
499
671
  && requests.every(request => request.healthProbe === true
500
- || request.path.startsWith('/v1/messages') && request.tools?.length === 0 && !request.inheritedInstructions),
501
- forgedToolRefused: forged.executionStopped && refused?.type === 'result' && refused.is_error === true && !existsSync(target),
672
+ || modelRoute(request) && (request.tools?.length === 0 || request.titleOnly === true) && !request.inheritedInstructions)
673
+ && requests.filter(request => modelRoute(request) && request.tools?.length === 0).length >= 2,
674
+ forgedToolRefused: forged.executionStopped && !existsSync(target) && (nativeGrok
675
+ ? parseGrokWorkerResult(forged.output).status !== 'complete' : refused?.type === 'result' && refused.is_error === true),
502
676
  };
503
677
  capability.verified = Object.values(checks).every(value => value === true);
504
678
  currentPhaseCapability(handle, false);
505
- return capability.verified ? { status: 'verified', provider: 'claude', identityDigest: handle.identityDigest,
679
+ return capability.verified ? { status: 'verified', provider: capability.provider, identityDigest: handle.identityDigest,
506
680
  canonicalWritePrevented: true, outputBoundEnforced: true, timeoutEnforced: true, networkIsolated: true, childProcessPrevented: true, toolsExposed: 0, inheritedCapabilitiesBlocked: true,
507
681
  forgedToolRefused: true, fixtureServiceOnly: true,
682
+ ...(nativeGrok ? { auxiliaryTools: requests.some(request => request.titleOnly) ? ['session_title'] : [],
683
+ toolsBoundary: 'zero worker tools; auxiliary title schema confined to private runtime' } : {}),
508
684
  authority: 'none' } : { ...unavailablePhase('phase-provider-conformance-failed'), checks,
509
- requests: requests.map(request => ({ route: request.healthProbe ? 'fixture-health' : request.path.startsWith('/v1/messages') ? 'fixture-model'
685
+ requests: requests.map(request => ({ route: request.healthProbe ? 'fixture-health' : modelRoute(request) ? 'fixture-model'
510
686
  : request.path.includes('forbidden-hook') ? 'forbidden-hook' : request.path.includes('forbidden-mcp') ? 'forbidden-mcp' : 'other',
511
- toolsExposed: request.tools?.length ?? null, inheritedInstructions: request.inheritedInstructions === true })) };
687
+ toolsExposed: request.tools?.length ?? null, toolFields: request.toolFields, toolNames: (request.tools ?? []).filter(name => /^[a-z_]{1,64}$/.test(name)), inheritedInstructions: request.inheritedInstructions === true })) };
512
688
  } catch (error) {
513
689
  capability.verified = false;
514
690
  return { ...unavailablePhase(['phase-cancelled', 'phase-execution-unknown'].includes(error.message)
@@ -1,4 +1,7 @@
1
+ import {resolveCodingProviders,codingPolicyDigest,assertModelPolicy} from './coding-providers.mjs';
2
+ // Keep legacy discovery defaults stable; Grok is an explicit, additive choice.
1
3
  export const VALIDATION_PROVIDERS = Object.freeze(['claude', 'codex', 'antigravity']);
4
+ export const SUPPORTED_VALIDATION_PROVIDERS = Object.freeze([...VALIDATION_PROVIDERS,'grok']);
2
5
 
3
6
  const LEGACY_PROVIDER_ALIASES = Object.freeze({
4
7
  gemini: 'antigravity',
@@ -104,7 +107,7 @@ function normaliseValidators(value, checkpoint) {
104
107
 
105
108
  const validators = [...new Set(value.map(canonicalValidationProvider))];
106
109
  for (const provider of validators) {
107
- requireEnum(provider, VALIDATION_PROVIDERS, `validation checkpoint ${checkpoint} validator`);
110
+ requireEnum(provider, SUPPORTED_VALIDATION_PROVIDERS, `validation checkpoint ${checkpoint} validator`);
108
111
  }
109
112
  return validators;
110
113
  }
@@ -177,7 +180,7 @@ export function normaliseValidationConfig(value = {}) {
177
180
 
178
181
  const rawProviders = value?.external?.providers ?? {};
179
182
  const providers = Object.fromEntries(
180
- VALIDATION_PROVIDERS.map((provider) => [
183
+ SUPPORTED_VALIDATION_PROVIDERS.filter(provider=>provider!=='grok'||Object.hasOwn(rawProviders,provider)).map((provider) => [
181
184
  provider,
182
185
  normaliseProvider(
183
186
  rawProviders[provider] ?? (provider === 'antigravity' ? rawProviders.gemini : undefined),
@@ -218,13 +221,13 @@ export function effectiveValidationConfig(configOrValidation, orchestrator = 'ma
218
221
  configOrValidation?.validation ?? configOrValidation,
219
222
  );
220
223
  const canonicalOrchestrator = canonicalValidationProvider(orchestrator);
221
- const normalisedOrchestrator = VALIDATION_PROVIDERS.includes(canonicalOrchestrator)
224
+ const normalisedOrchestrator = SUPPORTED_VALIDATION_PROVIDERS.includes(canonicalOrchestrator)
222
225
  ? canonicalOrchestrator
223
226
  : 'manual';
224
227
 
225
- const globallyEligible = VALIDATION_PROVIDERS.filter((provider) => {
228
+ const globallyEligible = SUPPORTED_VALIDATION_PROVIDERS.filter((provider) => {
226
229
  const setting = validation.external.providers[provider];
227
- return setting.state === 'available' && setting.enabled;
230
+ return setting?.state === 'available' && setting.enabled;
228
231
  });
229
232
 
230
233
  const checkpoints = Object.fromEntries(
@@ -232,7 +235,14 @@ export function effectiveValidationConfig(configOrValidation, orchestrator = 'ma
232
235
  const requested = setting.validators === 'auto'
233
236
  ? globallyEligible
234
237
  : setting.validators.filter((provider) => globallyEligible.includes(provider));
235
- const providers = requested.filter((provider) => provider !== normalisedOrchestrator);
238
+ let providers = requested.filter((provider) => provider !== normalisedOrchestrator);
239
+ if(setting.enabled && configOrValidation?.coding_providers){
240
+ providers=resolveCodingProviders(configOrValidation.coding_providers,{
241
+ orchestrator:normalisedOrchestrator,available:globallyEligible,reviewers:providers,
242
+ requiredReviewers:providers.length,
243
+ }).reviewers;
244
+ for(const provider of providers)assertModelPolicy(provider,configOrValidation.coding_providers);
245
+ }
236
246
 
237
247
  return [
238
248
  checkpoint,
@@ -255,11 +265,12 @@ export function effectiveValidationConfig(configOrValidation, orchestrator = 'ma
255
265
  orchestrator: normalisedOrchestrator,
256
266
  providers: clone(validation.external.providers),
257
267
  checkpoints,
268
+ ...(configOrValidation?.coding_providers ? {codingPolicy:clone(configOrValidation.coding_providers),codingPolicyDigest:codingPolicyDigest(configOrValidation.coding_providers)} : {}),
258
269
  };
259
270
  }
260
271
 
261
272
  export function setValidationProvider(config, provider, state, enabled = undefined) {
262
- requireEnum(provider, VALIDATION_PROVIDERS, 'validation provider');
273
+ requireEnum(provider, SUPPORTED_VALIDATION_PROVIDERS, 'validation provider');
263
274
  requireEnum(state, ['available', 'unavailable'], 'validation provider state');
264
275
  const validation = normaliseValidationConfig(config.validation);
265
276
  validation.external.providers[provider] = {
@@ -18,6 +18,9 @@ personas:
18
18
  approvals:
19
19
  build: required
20
20
  destructive_operations: required
21
+ # Optional coding-provider roles are configured with `ewai providers` or the
22
+ # dashboard. Absence preserves existing provider and native model selection.
23
+ # Settings do not grant execution authority; unsupported model restrictions stop.
21
24
  validation:
22
25
  standards:
23
26
  required: true