@praxis-ai/praxis 0.1.1 → 0.1.3

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 (49) hide show
  1. package/dist/agentCore/index.d.ts +45 -6
  2. package/dist/agentCore/index.js +14 -2
  3. package/dist/applicationLayer/applicationContract.d.ts +2 -0
  4. package/dist/applicationLayer/applicationRuntime.d.ts +13 -1
  5. package/dist/applicationLayer/applicationRuntime.js +39 -3
  6. package/dist/applicationLayer/index.d.ts +2 -0
  7. package/dist/applicationLayer/index.js +1 -0
  8. package/dist/basetool/core/shellRun.js +6 -1
  9. package/dist/rax_packageManager/raxCli.js +42 -1
  10. package/dist/runtimeImplementation/praxisRuntimeKernel.d.ts +13 -0
  11. package/dist/runtimeImplementation/praxisRuntimeKernel.js +550 -15
  12. package/dist/runtimeImplementation/runtime.componentPlane/runtimeComponentRegistry.d.ts +1 -1
  13. package/dist/runtimeImplementation/runtime.componentPlane/runtimeComponentRegistry.js +2 -2
  14. package/dist/runtimeImplementation/runtime.dependencyPlane/dependencySourceRegistry.d.ts +1 -1
  15. package/dist/runtimeImplementation/runtime.dependencyPlane/dependencySourceRegistry.js +12 -0
  16. package/dist/runtimeImplementation/runtime.dependencyPlane/dependencyTypes.js +2 -0
  17. package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.d.ts +3 -0
  18. package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.js +45 -7
  19. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.js +56 -0
  20. package/dist/runtimeImplementation/runtime.mcpPlane/index.d.ts +225 -0
  21. package/dist/runtimeImplementation/runtime.mcpPlane/index.js +549 -0
  22. package/dist/runtimeImplementation/runtime.sandboxPlane/baseToolSandboxPlanner.js +0 -2
  23. package/dist/runtimeImplementation/runtime.sandboxPlane/raxcellSandboxProvider.d.ts +19 -0
  24. package/dist/runtimeImplementation/runtime.sandboxPlane/raxcellSandboxProvider.js +172 -0
  25. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxCommandRunner.d.ts +13 -1
  26. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxCommandRunner.js +230 -186
  27. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxPolicyMiddleware.d.ts +175 -0
  28. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxPolicyMiddleware.js +142 -0
  29. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxRuntimeProvider.d.ts +9 -0
  30. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxRuntimeProvider.js +115 -205
  31. package/dist/runtimeImplementation/runtimeAgentManifest.js +7 -3
  32. package/package.json +3 -1
  33. package/raxode-tui/dist/raxode-cli/backend/agents/codingAgent/agent.js +3 -3
  34. package/raxode-tui/dist/raxode-cli/backend/application/backendModuleInventory.js +3 -3
  35. package/raxode-tui/dist/raxode-cli/backend/application/localReadinessProbe.d.ts +1 -0
  36. package/raxode-tui/dist/raxode-cli/backend/application/localReadinessProbe.js +50 -4
  37. package/raxode-tui/dist/raxode-cli/backend/application/raxcellSandboxProvider.d.ts +12 -0
  38. package/raxode-tui/dist/raxode-cli/backend/application/raxcellSandboxProvider.js +58 -0
  39. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.d.ts +1 -0
  40. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.js +3 -1
  41. package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.d.ts +2 -0
  42. package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.js +7 -0
  43. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.d.ts +2 -0
  44. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.js +21 -1
  45. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.d.ts +1 -1
  46. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.js +8 -0
  47. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.js +19 -1
  48. package/raxode-tui/package.json +2 -1
  49. package/tsconfig.json +16 -1
@@ -42,6 +42,7 @@ import { approvalInterfaceEnvelope, } from "../interfaceAdapter/interfaceEnvelop
42
42
  import { createInMemorySessionStateEventStore, createSqliteSessionStateEventStore, } from "./runtimeSessionStateEventStore.js";
43
43
  import { applyRaxStorageInitPlan, createStoragePlaneRuntime, } from "./runtime.storagePlane/storagePlaneRuntime.js";
44
44
  import { prepareSandboxRuntime, } from "./runtime.sandboxPlane/sandboxRuntimeProvider.js";
45
+ import { buildMcpServerProfilesFromManifest, createInMemoryMcpPlusOverlayStore, createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, learnedProfileFromProposal, mcp, mcpHarnessModuleFrom, planMcpHarnessExposure, } from "./runtime.mcpPlane/index.js";
45
46
  async function inferFilesystemActionForTool(input) {
46
47
  if (input.toolId !== "patch.apply")
47
48
  return undefined;
@@ -289,6 +290,316 @@ function defaultMcpServerId(toolId, args) {
289
290
  return undefined;
290
291
  return readString(args.serverId) ?? "local-mcp";
291
292
  }
293
+ function withRuntimeHarnessToolLayer(manifest, dynamicTools, reason) {
294
+ const byId = new Map();
295
+ for (const tool of [...manifest.harness.tools, ...dynamicTools]) {
296
+ byId.set(tool.toolId, tool);
297
+ }
298
+ return {
299
+ ...manifest,
300
+ harness: {
301
+ ...manifest.harness,
302
+ tools: [...byId.values()],
303
+ metadata: {
304
+ ...manifest.harness.metadata,
305
+ runtimeMcpDynamicToolCount: dynamicTools.length,
306
+ runtimeMcpDynamicToolRefreshReason: reason,
307
+ },
308
+ },
309
+ };
310
+ }
311
+ function withRuntimeMcpModule(manifest, module) {
312
+ if (module === undefined || manifest.harness.modules.mcp !== undefined)
313
+ return manifest;
314
+ const byId = new Map();
315
+ for (const tool of [...manifest.harness.tools, ...mcp.recommendedTools()]) {
316
+ byId.set(tool.toolId, tool);
317
+ }
318
+ return {
319
+ ...manifest,
320
+ harness: {
321
+ ...manifest.harness,
322
+ modules: {
323
+ ...manifest.harness.modules,
324
+ mcp: module,
325
+ },
326
+ tools: [...byId.values()],
327
+ runtimeRequirements: manifest.harness.runtimeRequirements.includes("runtime.mcp")
328
+ ? manifest.harness.runtimeRequirements
329
+ : [...manifest.harness.runtimeRequirements, "runtime.mcp"],
330
+ metadata: {
331
+ ...manifest.harness.metadata,
332
+ runtimeMcpModuleSource: "runtime.options.mcpModule",
333
+ },
334
+ },
335
+ };
336
+ }
337
+ function normalizeMcpNativeToolDeclaration(tool) {
338
+ if (!isRecord(tool))
339
+ return undefined;
340
+ const name = readString(tool.name);
341
+ if (name === undefined)
342
+ return undefined;
343
+ const description = readString(tool.description) ?? readString(tool.title) ?? name;
344
+ const schema = isRecord(tool.inputSchema) ? tool.inputSchema : isRecord(tool.input_schema) ? tool.input_schema : {};
345
+ return { name, description, inputSchema: schema };
346
+ }
347
+ function runtimeMcpPlusProjectId(input) {
348
+ if (typeof input.explicit === "string" && input.explicit.trim().length > 0)
349
+ return input.explicit.trim();
350
+ return `project.${createHash("sha256").update(path.resolve(input.workspaceRoot)).digest("hex").slice(0, 16)}`;
351
+ }
352
+ function mcpPlusOverlayToExposureState(overlay) {
353
+ if (overlay === undefined)
354
+ return {};
355
+ return {
356
+ mode: overlay.mode,
357
+ activeTools: [
358
+ ...overlay.activeTools,
359
+ ...(overlay.pendingReprofile ? ["mcp_plus.reprofile"] : []),
360
+ ],
361
+ };
362
+ }
363
+ function readStringList(value) {
364
+ if (!Array.isArray(value))
365
+ return [];
366
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
367
+ }
368
+ function proposalFromArgs(args, fallbackServerId) {
369
+ const rawToolCards = isRecord(args.toolCards) ? args.toolCards : undefined;
370
+ const toolCards = rawToolCards === undefined ? undefined : Object.fromEntries(Object.entries(rawToolCards).flatMap(([toolName, card]) => {
371
+ if (!isRecord(card))
372
+ return [];
373
+ return [[toolName, {
374
+ title: readString(card.title),
375
+ summary: readString(card.summary),
376
+ keywords: readStringList(card.keywords),
377
+ }]];
378
+ }));
379
+ const rawSkillChapters = Array.isArray(args.skillChapters) ? args.skillChapters : [];
380
+ const proposal = {
381
+ serverId: readString(args.serverId) ?? fallbackServerId,
382
+ pinnedTools: readStringList(args.pinnedTools),
383
+ warmTools: readStringList(args.warmTools),
384
+ indexedTools: readStringList(args.indexedTools),
385
+ alwaysIndexTools: readStringList(args.alwaysIndexTools),
386
+ toolCards,
387
+ skillChapters: rawSkillChapters.flatMap((chapter) => {
388
+ if (!isRecord(chapter))
389
+ return [];
390
+ const id = readString(chapter.id);
391
+ const title = readString(chapter.title);
392
+ const summary = readString(chapter.summary);
393
+ return id === undefined || title === undefined || summary === undefined ? [] : [{ id, title, summary }];
394
+ }),
395
+ rationale: readString(args.rationale),
396
+ };
397
+ if (Object.hasOwn(args, "modeHint")) {
398
+ return {
399
+ ...proposal,
400
+ modeHint: args.modeHint,
401
+ };
402
+ }
403
+ return proposal;
404
+ }
405
+ function mcpPlusServerSpec(manifest, serverId) {
406
+ return mcpHarnessModuleFrom(manifest.harness)?.servers.find((server) => server.serverId === serverId);
407
+ }
408
+ function createRuntimeMcpPlusController(input) {
409
+ let nativeInventory = {};
410
+ async function loadOverlay(serverId, now) {
411
+ return await input.overlayStore.load({ sessionId: input.sessionId, serverId }) ?? {
412
+ serverId,
413
+ sessionId: input.sessionId,
414
+ mode: "expanded",
415
+ activeTools: [],
416
+ counters: { consecutiveIndexedToolCalls: {} },
417
+ updatedAt: now,
418
+ };
419
+ }
420
+ return {
421
+ async setNativeInventory(inventory) {
422
+ nativeInventory = inventory;
423
+ },
424
+ async learnedProfilesByServerId(manifest) {
425
+ const module = mcpHarnessModuleFrom(manifest.harness);
426
+ const profiles = {};
427
+ for (const server of module?.servers ?? []) {
428
+ if (server.mode !== "mcp-plus" || server.manifest !== undefined)
429
+ continue;
430
+ profiles[server.serverId] = await input.profileStore.load({ projectId: input.projectId, serverId: server.serverId });
431
+ }
432
+ return profiles;
433
+ },
434
+ async exposureStateByServerId(manifest) {
435
+ const module = mcpHarnessModuleFrom(manifest.harness);
436
+ const states = {};
437
+ for (const server of module?.servers ?? []) {
438
+ if (server.mode !== "mcp-plus")
439
+ continue;
440
+ states[server.serverId] = mcpPlusOverlayToExposureState(await input.overlayStore.load({ sessionId: input.sessionId, serverId: server.serverId }));
441
+ }
442
+ return states;
443
+ },
444
+ async callControlTool(control) {
445
+ if (control.controlName === "mcp_plus.init" || control.controlName === "mcp_plus.reprofile") {
446
+ const proposal = proposalFromArgs(control.args, control.serverId);
447
+ const nativeTools = nativeInventory[proposal.serverId] ?? [];
448
+ const existing = await input.profileStore.load({ projectId: input.projectId, serverId: proposal.serverId });
449
+ const learned = learnedProfileFromProposal({
450
+ proposal,
451
+ nativeTools,
452
+ projectId: input.projectId,
453
+ now: control.now,
454
+ existing,
455
+ });
456
+ if (!learned.ok)
457
+ return { ok: false, error: learned.error };
458
+ await input.profileStore.save({ projectId: input.projectId, serverId: proposal.serverId }, learned.profile);
459
+ const overlay = await loadOverlay(proposal.serverId, control.now);
460
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId: proposal.serverId }, {
461
+ ...overlay,
462
+ mode: "expanded",
463
+ activeTools: [],
464
+ pendingReprofile: false,
465
+ counters: { consecutiveIndexedToolCalls: {} },
466
+ updatedAt: control.now,
467
+ metadata: {
468
+ ...(overlay.metadata ?? {}),
469
+ lastProfileControl: control.controlName,
470
+ },
471
+ });
472
+ return {
473
+ ok: true,
474
+ output: {
475
+ accepted: true,
476
+ serverId: proposal.serverId,
477
+ projectId: input.projectId,
478
+ schemaVersion: learned.profile.schemaVersion,
479
+ controlName: control.controlName,
480
+ },
481
+ metadata: { serverId: proposal.serverId, projectId: input.projectId, controlName: control.controlName },
482
+ };
483
+ }
484
+ if (control.controlName === "mcp_plus.skill_read") {
485
+ const serverId = readString(control.args.serverId) ?? control.serverId;
486
+ const notes = await input.skillStore.read({ serverId, projectId: input.projectId }, {
487
+ id: readString(control.args.id),
488
+ chapter: readString(control.args.chapter),
489
+ });
490
+ return { ok: true, output: { serverId, projectId: input.projectId, notes } };
491
+ }
492
+ if (control.controlName === "mcp_plus.skill_write" || control.controlName === "mcp_plus.finish") {
493
+ const rawSkill = control.controlName === "mcp_plus.finish" && isRecord(control.args.skill)
494
+ ? control.args.skill
495
+ : control.args;
496
+ const serverId = readString(rawSkill.serverId) ?? readString(control.args.serverId) ?? control.serverId;
497
+ const chapter = readString(rawSkill.chapter);
498
+ const title = readString(rawSkill.title);
499
+ const summary = readString(rawSkill.summary);
500
+ if (chapter === undefined || title === undefined || summary === undefined) {
501
+ return {
502
+ ok: true,
503
+ output: {
504
+ accepted: false,
505
+ serverId,
506
+ projectId: input.projectId,
507
+ reason: "No complete skill note was provided.",
508
+ },
509
+ };
510
+ }
511
+ const note = await input.skillStore.write({ serverId, projectId: input.projectId }, {
512
+ chapter,
513
+ title,
514
+ summary,
515
+ whenToUse: readString(rawSkill.whenToUse),
516
+ do: readStringList(rawSkill.do),
517
+ why: readString(rawSkill.why),
518
+ avoid: readStringList(rawSkill.avoid),
519
+ pitfalls: readStringList(rawSkill.pitfalls),
520
+ });
521
+ return { ok: true, output: { accepted: true, serverId, projectId: input.projectId, note } };
522
+ }
523
+ if (control.controlName === "mcp_plus.expand") {
524
+ const serverId = readString(control.args.server) ?? readString(control.args.serverId) ?? control.serverId;
525
+ const request = readString(control.args.request)?.toLowerCase() ?? "";
526
+ const server = mcpPlusServerSpec(control.manifest, serverId);
527
+ const profile = await input.profileStore.load({ projectId: input.projectId, serverId });
528
+ const indexedTools = server?.manifest?.exposure?.indexedTools ?? profile?.exposure.indexedTools ?? [];
529
+ const activatedTools = request.length === 0
530
+ ? indexedTools
531
+ : indexedTools.filter((toolName) => toolName.toLowerCase().includes(request));
532
+ const overlay = await loadOverlay(serverId, control.now);
533
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId }, {
534
+ ...overlay,
535
+ mode: "expanded",
536
+ activeTools: [...new Set([...overlay.activeTools, ...activatedTools])],
537
+ updatedAt: control.now,
538
+ });
539
+ return { ok: true, output: { serverId, activatedTools, mode: "expanded" } };
540
+ }
541
+ return { ok: false, error: { code: "MCP_PLUS_CONTROL_UNKNOWN", message: `Unknown MCP+ control tool: ${control.controlName}`, publicSafe: true } };
542
+ },
543
+ async recordToolCall(record) {
544
+ if (!record.ok)
545
+ return false;
546
+ const toolSpec = record.manifest.harness.tools.find((tool) => tool.toolId === record.toolId);
547
+ if (toolSpec?.metadata?.toolProviderKind !== "mcp-static")
548
+ return false;
549
+ const serverId = readString(toolSpec.metadata.serverId);
550
+ const nativeToolName = readString(toolSpec.metadata.nativeToolName);
551
+ if (serverId === undefined || nativeToolName === undefined)
552
+ return false;
553
+ const server = mcpPlusServerSpec(record.manifest, serverId);
554
+ if (server?.mode !== "mcp-plus")
555
+ return false;
556
+ const profile = server.manifest === undefined
557
+ ? await input.profileStore.load({ projectId: input.projectId, serverId })
558
+ : undefined;
559
+ const indexedTools = new Set(server.manifest?.exposure?.indexedTools ?? profile?.exposure.indexedTools ?? []);
560
+ const overlay = await loadOverlay(serverId, record.now);
561
+ const consecutiveIndexedToolCalls = { ...overlay.counters.consecutiveIndexedToolCalls };
562
+ let pendingReprofile = overlay.pendingReprofile;
563
+ if (indexedTools.has(nativeToolName)) {
564
+ consecutiveIndexedToolCalls[nativeToolName] = (consecutiveIndexedToolCalls[nativeToolName] ?? 0) + 1;
565
+ if (consecutiveIndexedToolCalls[nativeToolName] >= input.reprofileConsecutiveIndexedCalls) {
566
+ pendingReprofile = true;
567
+ }
568
+ }
569
+ else {
570
+ for (const toolName of Object.keys(consecutiveIndexedToolCalls)) {
571
+ consecutiveIndexedToolCalls[toolName] = 0;
572
+ }
573
+ }
574
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId }, {
575
+ ...overlay,
576
+ mode: "expanded",
577
+ activeTools: [...new Set([...overlay.activeTools, nativeToolName])],
578
+ pendingReprofile,
579
+ counters: { consecutiveIndexedToolCalls },
580
+ updatedAt: record.now,
581
+ });
582
+ return true;
583
+ },
584
+ };
585
+ }
586
+ async function discoverRuntimeMcpDynamicTools(manifest, executor, mcpPlusRuntime) {
587
+ const profiles = buildMcpServerProfilesFromManifest(manifest);
588
+ if (profiles.length === 0 || executor.mcp?.listTools === undefined)
589
+ return [];
590
+ const inventory = {};
591
+ for (const profile of profiles) {
592
+ const listed = await executor.mcp.listTools({ serverId: profile.serverId });
593
+ if (listed?.ok !== true)
594
+ continue;
595
+ const rawTools = Array.isArray(listed.output?.tools) ? listed.output.tools : [];
596
+ inventory[profile.serverId] = rawTools
597
+ .map(normalizeMcpNativeToolDeclaration)
598
+ .filter((tool) => tool !== undefined);
599
+ }
600
+ await mcpPlusRuntime?.setNativeInventory(inventory);
601
+ return planMcpHarnessExposure(manifest, inventory, await mcpPlusRuntime?.exposureStateByServerId(manifest) ?? {}, await mcpPlusRuntime?.learnedProfilesByServerId(manifest) ?? {}).servers.flatMap((server) => [...server.dynamicToolSpecs]);
602
+ }
292
603
  function providerToolMappings(manifest) {
293
604
  return createProviderToolMappings(manifest.harness.tools);
294
605
  }
@@ -523,30 +834,54 @@ function shellCommandPathScanSource(command) {
523
834
  function isShellCommandAllowedSystemPath(token) {
524
835
  return token === "/dev/null";
525
836
  }
837
+ function escapeRegExp(value) {
838
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
839
+ }
526
840
  function shellCommandAbsolutePathTokens(command) {
527
841
  const matches = shellCommandPathScanSource(command).matchAll(/(^|[\s"'`=({[,;|&<>])\/(?!\/)[^\s"'`$;&|<>()[\]{}]*/gu);
528
842
  return [...matches]
529
843
  .map((match) => stripShellPathToken((match[0] ?? "").slice(match[1]?.length ?? 0)))
530
844
  .filter((token) => token.length > 1 && !token.includes("\0") && !isShellCommandAllowedSystemPath(token));
531
845
  }
846
+ function shellCommandPathAccess(command, token) {
847
+ const source = shellCommandPathScanSource(command);
848
+ const escaped = escapeRegExp(token);
849
+ const quotedToken = `["']?${escaped}["']?`;
850
+ if (new RegExp(`(?:^|[\\s;|&])(?:\\d?>|\\d?>>|>|>>|<>)\\s*${quotedToken}(?:\\s|$|[;&|])`, "u").test(source)) {
851
+ return "write";
852
+ }
853
+ if (new RegExp(`\\btee\\b[^\\n;&|]*${quotedToken}`, "u").test(source))
854
+ return "write";
855
+ if (new RegExp(`\\bsed\\b[^\\n;&|]*\\s-i\\b[^\\n;&|]*${quotedToken}`, "u").test(source))
856
+ return "write";
857
+ if (new RegExp(`\\b(?:touch|mkdir|rm|rmdir|chmod|chown)\\b[^\\n;&|]*${quotedToken}`, "u").test(source))
858
+ return "write";
859
+ if (new RegExp(`Path\\(\\s*["']${escaped}["']\\s*\\)[\\s\\S]*\\b(?:write_text|append_text)\\s*\\(`, "u").test(source)) {
860
+ return "write";
861
+ }
862
+ return "read";
863
+ }
532
864
  function shellCommandOutsideAllowedRootsMetadata(input) {
533
865
  const output = [];
534
866
  for (const token of shellCommandAbsolutePathTokens(input.command)) {
535
867
  const normalizedPath = path.resolve(token);
536
868
  if (isInsideAllowedRoots(normalizedPath, input.allowedRoots))
537
869
  continue;
538
- output.push(workspacePathMetadata({
539
- ok: false,
540
- reason: "OUTSIDE_ALLOWED_ROOTS",
541
- message: "shell command references an absolute path outside runtime allowed roots",
542
- requestedPath: token,
543
- normalizedPath,
544
- workspaceRoot: input.workspaceRoot,
545
- allowedRoots: input.allowedRoots,
546
- pathWasMapped: false,
547
- mappingSource: "absolute",
548
- suggestedCwd: input.workspaceRoot,
549
- }, "path"));
870
+ output.push({
871
+ ...workspacePathMetadata({
872
+ ok: false,
873
+ reason: "OUTSIDE_ALLOWED_ROOTS",
874
+ message: "shell command references an absolute path outside runtime allowed roots",
875
+ requestedPath: token,
876
+ normalizedPath,
877
+ workspaceRoot: input.workspaceRoot,
878
+ allowedRoots: input.allowedRoots,
879
+ pathWasMapped: false,
880
+ mappingSource: "absolute",
881
+ suggestedCwd: input.workspaceRoot,
882
+ }, "path"),
883
+ workspacePathAccess: shellCommandPathAccess(input.command, token),
884
+ });
550
885
  }
551
886
  return output;
552
887
  }
@@ -557,7 +892,6 @@ function normalizeWorkspacePathContract(input) {
557
892
  const allowedRoots = normalizeAllowedRoots({ workspaceRoot, allowedRoots: input.allowedRoots });
558
893
  const nextArgs = { ...input.args };
559
894
  const normalizations = [];
560
- const pathAccess = input.toolId === "patch.apply" ? "write" : "read";
561
895
  const allowOutsideAllowedRoots = input.profile !== undefined;
562
896
  if (input.toolId === "file.read" || input.toolId === "skill.load" || input.toolId === "media.viewImage") {
563
897
  for (const field of ["path", "filePath", "imagePath"]) {
@@ -596,12 +930,15 @@ function normalizeWorkspacePathContract(input) {
596
930
  }
597
931
  }
598
932
  const workspaceOutsideAllowedRoots = normalizations.some((item) => item.workspaceOutsideAllowedRoots === true || item.reason === "OUTSIDE_ALLOWED_ROOTS");
933
+ const workspacePathAccess = input.toolId === "patch.apply" || normalizations.some((item) => item.workspacePathAccess === "write")
934
+ ? "write"
935
+ : "read";
599
936
  return {
600
937
  ok: true,
601
938
  args: withWorkspaceNormalizationMetadata(nextArgs, normalizations, workspaceOutsideAllowedRoots
602
939
  ? {
603
940
  workspaceOutsideAllowedRoots: true,
604
- workspacePathAccess: pathAccess,
941
+ workspacePathAccess,
605
942
  }
606
943
  : {}),
607
944
  metadata: normalizations[0] === undefined
@@ -2132,6 +2469,7 @@ async function prepareKernelSandbox(input) {
2132
2469
  const sandbox = await prepareSandboxRuntime(input.manifest.sandbox, {
2133
2470
  cwd: input.options.sandbox?.cwd ?? input.storageRuntime.layout.workspace.root,
2134
2471
  runSmoke: input.options.sandbox?.runSmoke ?? false,
2472
+ providerReady: input.options.sandbox?.provider !== undefined,
2135
2473
  });
2136
2474
  input.events.push(...sandbox.events);
2137
2475
  await input.store.appendEvent(event(input.sessionId, "event:sandbox.prepared", "runtime.sandboxPlane.prepared", input.now(), { sandbox }));
@@ -2456,6 +2794,166 @@ function compactRequestMaterials(input) {
2456
2794
  return [...originalMaterials, ...governedSessionSummaryMaterial, ...governedProjectContext];
2457
2795
  }
2458
2796
  async function executeBaseToolDecision(input) {
2797
+ const dynamicToolSpec = input.manifest.harness.tools.find((tool) => tool.toolId === input.toolId);
2798
+ const dynamicRuntimeToolId = typeof dynamicToolSpec?.metadata?.runtimeToolId === "string"
2799
+ ? dynamicToolSpec.metadata.runtimeToolId
2800
+ : undefined;
2801
+ const dynamicRuntimeArguments = isRecord(dynamicToolSpec?.metadata?.runtimeArguments)
2802
+ ? dynamicToolSpec.metadata.runtimeArguments
2803
+ : undefined;
2804
+ if (dynamicToolSpec?.metadata?.toolProviderKind === "mcp-plus-control") {
2805
+ const serverId = readString(dynamicToolSpec.metadata.serverId);
2806
+ const controlName = readString(dynamicToolSpec.metadata.controlName);
2807
+ if (serverId === undefined || controlName === undefined || input.mcpPlusRuntime === undefined) {
2808
+ const record = {
2809
+ callId: input.toolCallId,
2810
+ toolId: input.toolId,
2811
+ arguments: input.args,
2812
+ ok: false,
2813
+ error: {
2814
+ code: "MCP_PLUS_CONTROL_UNAVAILABLE",
2815
+ message: "MCP+ control tool is not available in this runtime.",
2816
+ publicSafe: true,
2817
+ },
2818
+ };
2819
+ const observation = createObservationMaterial({
2820
+ observationId: `${input.sessionId}:observation:${input.toolCallId}`,
2821
+ source: "baseTool",
2822
+ status: "failed",
2823
+ title: `MCP+ control ${controlName ?? input.toolId}`,
2824
+ summary: "MCP+ control tool is not available.",
2825
+ refs: [input.toolCallId, input.toolId],
2826
+ payload: record.error,
2827
+ metadata: metadataRecord({ toolCallId: input.toolCallId, toolId: input.toolId }),
2828
+ });
2829
+ return {
2830
+ record,
2831
+ observation,
2832
+ events: ["runtime.mcpPlus.control.unavailable"],
2833
+ governance: {
2834
+ kind: "runtime.execEngine.baseTool.governanceDecision",
2835
+ toolId: input.toolId,
2836
+ status: "deny",
2837
+ risk: "safe",
2838
+ policyProfile: input.manifest.toolPolicy.profile,
2839
+ policyMatrixId: input.manifest.toolPolicy.matrixId,
2840
+ approvalRequired: false,
2841
+ approvalReason: "MCP+ control tool is not available.",
2842
+ sandbox: {
2843
+ sandboxId: input.manifest.sandbox.sandboxId,
2844
+ profile: input.manifest.sandbox.profile,
2845
+ providerFamily: input.manifest.sandbox.providerFamily,
2846
+ isolationLevel: input.manifest.sandbox.isolationLevel,
2847
+ filesystem: input.manifest.sandbox.filesystem,
2848
+ network: input.manifest.sandbox.network,
2849
+ shell: input.manifest.sandbox.shell,
2850
+ hostObserved: input.manifest.sandbox.profile === "host-observed",
2851
+ dependencyRefs: input.manifest.sandbox.dependencyRefs ?? [],
2852
+ },
2853
+ resourceLimits: input.manifest.sandbox.resourceLimits,
2854
+ publicSafe: true,
2855
+ events: ["runtime.mcpPlus.control.unavailable"],
2856
+ metadata: { toolCallId: input.toolCallId },
2857
+ },
2858
+ };
2859
+ }
2860
+ const controlResult = await input.mcpPlusRuntime.callControlTool({
2861
+ manifest: input.manifest,
2862
+ serverId,
2863
+ controlName,
2864
+ args: input.args,
2865
+ now: input.now(),
2866
+ });
2867
+ const record = {
2868
+ callId: input.toolCallId,
2869
+ toolId: input.toolId,
2870
+ arguments: input.args,
2871
+ ok: controlResult.ok,
2872
+ ...(controlResult.ok ? { output: controlResult.output } : { error: controlResult.error }),
2873
+ };
2874
+ const controlFailureMessage = controlResult.ok ? undefined : controlResult.error?.message ?? "MCP+ control tool failed.";
2875
+ const observation = createObservationMaterial({
2876
+ observationId: `${input.sessionId}:observation:${input.toolCallId}`,
2877
+ source: "baseTool",
2878
+ status: controlResult.ok ? "completed" : "failed",
2879
+ title: `MCP+ control ${controlName}`,
2880
+ summary: controlResult.ok ? "MCP+ control tool completed." : controlFailureMessage ?? "MCP+ control tool failed.",
2881
+ refs: [input.toolCallId, input.toolId],
2882
+ payload: controlResult.ok ? controlResult.output : controlResult.error ?? { code: "MCP_PLUS_CONTROL_FAILED", message: controlFailureMessage, publicSafe: true },
2883
+ metadata: metadataRecord({
2884
+ toolCallId: input.toolCallId,
2885
+ toolId: input.toolId,
2886
+ serverId,
2887
+ controlName,
2888
+ observationStatus: controlResult.ok ? "completed" : "failed",
2889
+ }),
2890
+ });
2891
+ return {
2892
+ record,
2893
+ observation,
2894
+ events: [controlResult.ok ? "runtime.mcpPlus.control.completed" : "runtime.mcpPlus.control.failed"],
2895
+ governance: {
2896
+ kind: "runtime.execEngine.baseTool.governanceDecision",
2897
+ toolId: input.toolId,
2898
+ status: controlResult.ok ? "allow" : "deny",
2899
+ risk: "safe",
2900
+ policyProfile: input.manifest.toolPolicy.profile,
2901
+ policyMatrixId: input.manifest.toolPolicy.matrixId,
2902
+ approvalRequired: false,
2903
+ sandbox: {
2904
+ sandboxId: input.manifest.sandbox.sandboxId,
2905
+ profile: input.manifest.sandbox.profile,
2906
+ providerFamily: input.manifest.sandbox.providerFamily,
2907
+ isolationLevel: input.manifest.sandbox.isolationLevel,
2908
+ filesystem: input.manifest.sandbox.filesystem,
2909
+ network: input.manifest.sandbox.network,
2910
+ shell: input.manifest.sandbox.shell,
2911
+ hostObserved: input.manifest.sandbox.profile === "host-observed",
2912
+ dependencyRefs: input.manifest.sandbox.dependencyRefs ?? [],
2913
+ },
2914
+ resourceLimits: input.manifest.sandbox.resourceLimits,
2915
+ publicSafe: true,
2916
+ events: [controlResult.ok ? "runtime.mcpPlus.control.completed" : "runtime.mcpPlus.control.failed"],
2917
+ metadata: { serverId, controlName },
2918
+ },
2919
+ };
2920
+ }
2921
+ if (dynamicRuntimeToolId !== undefined && dynamicRuntimeToolId !== input.toolId) {
2922
+ const delegatedArgs = dynamicRuntimeToolId === "mcp.use"
2923
+ ? {
2924
+ ...(dynamicRuntimeArguments ?? {}),
2925
+ arguments: input.args,
2926
+ }
2927
+ : {
2928
+ ...(dynamicRuntimeArguments ?? {}),
2929
+ ...input.args,
2930
+ };
2931
+ const delegated = await executeBaseToolDecision({
2932
+ ...input,
2933
+ toolId: dynamicRuntimeToolId,
2934
+ args: delegatedArgs,
2935
+ mcpPlusRuntime: input.mcpPlusRuntime,
2936
+ });
2937
+ return {
2938
+ ...delegated,
2939
+ record: {
2940
+ ...delegated.record,
2941
+ toolId: input.toolId,
2942
+ arguments: input.args,
2943
+ },
2944
+ observation: {
2945
+ ...delegated.observation,
2946
+ material: {
2947
+ ...delegated.observation.material,
2948
+ metadata: {
2949
+ ...(delegated.observation.material.metadata ?? {}),
2950
+ dynamicRuntimeToolId,
2951
+ dynamicMcpToolId: input.toolId,
2952
+ },
2953
+ },
2954
+ },
2955
+ };
2956
+ }
2459
2957
  let toolArguments = enrichToolArguments(input.manifest, input.toolId, input.args, {
2460
2958
  runtimeId: input.runtimeId,
2461
2959
  sessionId: input.sessionId,
@@ -3383,6 +3881,8 @@ export class PraxisRuntimeKernel {
3383
3881
  }),
3384
3882
  });
3385
3883
  const dryRun = options.dryRun !== false;
3884
+ manifest = withRuntimeMcpModule(manifest, options.mcpModule);
3885
+ const runtimeMcpBaseManifest = manifest;
3386
3886
  const defaultBaseToolPolicy = {
3387
3887
  workspaceRoot: toolWorkspaceRoot,
3388
3888
  allowedRoots: toolAllowedRoots,
@@ -3413,7 +3913,15 @@ export class PraxisRuntimeKernel {
3413
3913
  sandboxSpec: manifest.sandbox,
3414
3914
  preparedSandbox: sandboxPrepared.sandbox,
3415
3915
  policyProfile: manifest.toolPolicy.profile,
3916
+ sandboxProvider: options.sandbox?.provider,
3917
+ sandboxAudit: async (sandboxEvent) => {
3918
+ await store.appendEvent(event(sessionId, `event:sandbox:${sandboxEvent.actionId}:${sandboxEvent.type}`, sandboxEvent.type, now(), { sandbox: sandboxEvent }));
3919
+ },
3416
3920
  remoteSandboxWorker: options.sandbox?.remoteWorker,
3921
+ mcpServers: [
3922
+ ...buildMcpServerProfilesFromManifest(manifest),
3923
+ ...(options.mcpServers ?? []),
3924
+ ],
3417
3925
  emitEvent: (runtimeEvent) => {
3418
3926
  events.push(runtimeEvent.type);
3419
3927
  },
@@ -3423,9 +3931,25 @@ export class PraxisRuntimeKernel {
3423
3931
  id: "praxis-runtime-kernel",
3424
3932
  sessionId,
3425
3933
  };
3934
+ const mcpPlusRuntime = createRuntimeMcpPlusController({
3935
+ sessionId,
3936
+ projectId: runtimeMcpPlusProjectId({
3937
+ explicit: options.mcpPlus?.projectId,
3938
+ workspaceRoot: toolWorkspaceRoot,
3939
+ }),
3940
+ profileStore: options.mcpPlus?.profileStore ?? createFileMcpPlusProfileStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
3941
+ overlayStore: options.mcpPlus?.overlayStore ?? createInMemoryMcpPlusOverlayStore(),
3942
+ skillStore: options.mcpPlus?.skillStore ?? createFileMcpPlusSkillStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
3943
+ reprofileConsecutiveIndexedCalls: options.mcpPlus?.reprofileConsecutiveIndexedCalls ?? 6,
3944
+ });
3426
3945
  const maxModelTurns = manifest.harness.loop.maxModelTurns ?? 2;
3427
3946
  const maxToolCalls = manifest.harness.loop.maxToolCalls ?? 4;
3428
- const toolMappings = providerToolMappings(manifest);
3947
+ let toolMappings = [];
3948
+ async function refreshRuntimeMcpTools(reason) {
3949
+ manifest = withRuntimeHarnessToolLayer(runtimeMcpBaseManifest, await discoverRuntimeMcpDynamicTools(runtimeMcpBaseManifest, executor, mcpPlusRuntime), reason);
3950
+ toolMappings = providerToolMappings(manifest);
3951
+ }
3952
+ await refreshRuntimeMcpTools("session.checkpoint.start");
3429
3953
  const providerFamily = providerToolSchemaFamilyForModel(manifest.model);
3430
3954
  let toolContextSelection = {
3431
3955
  families: normalizedSelection(options.toolContextSelection?.families),
@@ -4607,6 +5131,7 @@ export class PraxisRuntimeKernel {
4607
5131
  store,
4608
5132
  approvalResolver: options.approvalResolver,
4609
5133
  agentReviewResolver: options.agentReviewResolver,
5134
+ mcpPlusRuntime,
4610
5135
  preparedSandbox: sandboxPrepared.sandbox,
4611
5136
  dependencyRuntime: options.baseToolDependencyRuntime,
4612
5137
  now,
@@ -4619,6 +5144,16 @@ export class PraxisRuntimeKernel {
4619
5144
  });
4620
5145
  toolCalls.push(executed.record);
4621
5146
  observations.push(executed.observation);
5147
+ const mcpPlusToolCallUpdated = await mcpPlusRuntime.recordToolCall({
5148
+ manifest,
5149
+ toolId: executed.record.toolId,
5150
+ ok: executed.record.ok,
5151
+ now: now(),
5152
+ });
5153
+ if (executed.record.ok && (mcpPlusToolCallUpdated ||
5154
+ manifest.harness.tools.find((tool) => tool.toolId === executed.record.toolId)?.metadata?.toolProviderKind === "mcp-plus-control")) {
5155
+ await refreshRuntimeMcpTools("session.checkpoint.after_mcp_tool");
5156
+ }
4622
5157
  if (invalidatesFileReadCache(executed.record.toolId)) {
4623
5158
  sameTurnFileReadCache.clear();
4624
5159
  }
@@ -3,7 +3,7 @@ import type { RuntimeComponentSpec } from "./componentTypes.js";
3
3
  export declare const officialRuntimeComponents: readonly [{
4
4
  readonly componentId: "component.sandbox.bubblewrap";
5
5
  readonly kind: "sandbox";
6
- readonly title: "Linux bubblewrap sandbox";
6
+ readonly title: "Linux Raxcell sandbox provider";
7
7
  readonly dependencies: readonly [DependencyDeclaration];
8
8
  readonly fallbackComponentIds: readonly ["component.sandbox.workspaceRollback"];
9
9
  readonly supportedPlatforms: readonly ["linux"];