@praxis-ai/praxis 0.1.4 → 0.1.6

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 (64) hide show
  1. package/dist/agentCore/index.d.ts +29 -3
  2. package/dist/agentCore/index.js +3 -0
  3. package/dist/applicationLayer/applicationRuntime.js +7 -1
  4. package/dist/basetool/authoring.d.ts +2 -0
  5. package/dist/basetool/authoring.js +2 -0
  6. package/dist/basetool/catalog.d.ts +1 -1
  7. package/dist/basetool/catalog.js +86 -4
  8. package/dist/basetool/core/index.d.ts +4 -2
  9. package/dist/basetool/core/index.js +8 -0
  10. package/dist/basetool/core/mcpCompletions.d.ts +2 -0
  11. package/dist/basetool/core/mcpCompletions.js +70 -0
  12. package/dist/basetool/core/mcpPrompts.d.ts +2 -0
  13. package/dist/basetool/core/mcpPrompts.js +48 -0
  14. package/dist/basetool/core/mcpResources.js +41 -5
  15. package/dist/basetool/profiles.js +15 -1
  16. package/dist/basetool/registry.d.ts +1 -1
  17. package/dist/basetool/supportCatalog.js +23 -6
  18. package/dist/executionEngine/promptPack/promptAssembler.js +1 -0
  19. package/dist/executionEngine/promptPack/promptDefiner.d.ts +4 -4
  20. package/dist/executionEngine/promptPack/promptDefiner.js +1 -0
  21. package/dist/runtimeImplementation/praxisRuntimeKernel.d.ts +4 -0
  22. package/dist/runtimeImplementation/praxisRuntimeKernel.js +1729 -1498
  23. package/dist/runtimeImplementation/runtime.execEngine/baseToolApprovalScope.js +11 -0
  24. package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.js +13 -1
  25. package/dist/runtimeImplementation/runtime.execEngine/baseToolPolicyAdjudicator.js +14 -0
  26. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.d.ts +27 -0
  27. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.js +648 -56
  28. package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.d.ts +2 -0
  29. package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.js +35 -0
  30. package/dist/runtimeImplementation/runtime.mcpPlane/index.d.ts +20 -7
  31. package/dist/runtimeImplementation/runtime.mcpPlane/index.js +105 -89
  32. package/dist/runtimeImplementation/runtime.skillPlane/index.d.ts +119 -0
  33. package/dist/runtimeImplementation/runtime.skillPlane/index.js +274 -0
  34. package/dist/runtimeImplementation/runtimeAgentManifest.js +2 -0
  35. package/dist/toolBase/catalog.d.ts +24 -0
  36. package/dist/toolBase/catalog.js +41 -3
  37. package/dist/toolBase/profiles.d.ts +3 -3
  38. package/dist/toolBase/profiles.js +2 -0
  39. package/dist/toolBase/types.d.ts +1 -1
  40. package/examples/fullstack/agents/repoInspector/harness/repoInspectorHarness.ts +23 -0
  41. package/examples/raxode-mcp-plus-ten-server.config.json +229 -0
  42. package/examples/scripts/README.md +8 -2
  43. package/examples/scripts/mcp-plus-native-smoke.ts +1296 -0
  44. package/package.json +3 -1
  45. package/raxode-tui/dist/raxode-cli/backend/agents/codingAgent/prompts/tool-use.md +1 -1
  46. package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.d.ts +9 -0
  47. package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.js +65 -0
  48. package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.d.ts +28 -0
  49. package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.js +57 -0
  50. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.d.ts +5 -1
  51. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.js +40 -0
  52. package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.js +6 -0
  53. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.d.ts +4 -0
  54. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.js +14 -0
  55. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.d.ts +1 -1
  56. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.js +16 -1
  57. package/raxode-tui/dist/raxode-cli/contracts.d.ts +1 -0
  58. package/raxode-tui/dist/raxode-cli/frontend/bridge/readiness.js +24 -0
  59. package/raxode-tui/dist/raxode-cli/frontend/tui/app/direct-tui.js +35 -0
  60. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.d.ts +2 -0
  61. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.js +8 -0
  62. package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.d.ts +31 -0
  63. package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.js +129 -0
  64. package/raxode-tui/dist/raxode-cli/index.d.ts +1 -0
@@ -36,6 +36,8 @@ export type PromptContextAssemblyRequest = {
36
36
  sessionSummary?: PromptContextSessionSummary;
37
37
  conversationWindow?: readonly PromptContextConversationMessage[];
38
38
  projectContextGovernanceMaterials?: readonly PromptPackMaterialDraft[];
39
+ toolDeclarationPreludeMaterials?: readonly PromptPackMaterialDraft[];
40
+ skillIndexMaterials?: readonly PromptPackMaterialDraft[];
39
41
  budget?: PromptContextAssemblyBudget;
40
42
  toolContextSelection?: BaseToolContextSelection;
41
43
  toolContextUsage?: readonly BaseToolContextUsageRecord[];
@@ -418,6 +418,37 @@ export function assemblePromptContextMaterials(input) {
418
418
  sandboxMode: input.manifest.sandbox?.profile,
419
419
  toolSpecificGuidance: PRAXIS_BASE_TOOL_CALLING_PROTOCOL,
420
420
  });
421
+ const toolDeclarationPreludeMaterials = (input.toolDeclarationPreludeMaterials ?? []).map((material, index) => ({
422
+ ...material,
423
+ id: material.id ?? `runtime:tool-declaration-prelude:${index + 1}`,
424
+ kind: material.kind,
425
+ source: material.source ?? "runtime.toolDeclarationPrelude",
426
+ sourceCategory: material.sourceCategory ?? "declared-built-in",
427
+ priority: material.priority ?? 95.5 - index,
428
+ trusted: material.trusted ?? true,
429
+ scope: material.scope ?? "runtime.toolCalling",
430
+ promptSegmentKind: "toolDeclarations",
431
+ metadata: {
432
+ ...(material.metadata ?? {}),
433
+ promptSegmentKind: "toolDeclarations",
434
+ toolMaterialType: "policy",
435
+ },
436
+ }));
437
+ const skillIndexMaterials = (input.skillIndexMaterials ?? []).map((material, index) => ({
438
+ ...material,
439
+ id: material.id ?? `runtime:skill-index:${index + 1}`,
440
+ kind: material.kind,
441
+ source: material.source ?? "runtime.skillPlane.index",
442
+ sourceCategory: material.sourceCategory ?? "declared-built-in",
443
+ priority: material.priority ?? 94 - index,
444
+ trusted: material.trusted ?? true,
445
+ scope: material.scope ?? "runtime.skillPlane.index",
446
+ promptSegmentKind: "skillIndex",
447
+ metadata: {
448
+ ...(material.metadata ?? {}),
449
+ promptSegmentKind: "skillIndex",
450
+ },
451
+ }));
421
452
  const sessionSummaryMaterial = input.sessionSummary === undefined
422
453
  ? []
423
454
  : [{
@@ -445,6 +476,8 @@ export function assemblePromptContextMaterials(input) {
445
476
  ...projectContextGovernanceMaterials,
446
477
  ...sessionSummaryMaterial,
447
478
  ...memoryContextMaterials,
479
+ ...toolDeclarationPreludeMaterials,
480
+ ...skillIndexMaterials,
448
481
  {
449
482
  id: `task:${input.turnIndex}`,
450
483
  kind: "user",
@@ -526,6 +559,8 @@ export function assemblePromptContextMaterials(input) {
526
559
  ...manifestPromptMaterials,
527
560
  declaredRuntimeContextMaterial,
528
561
  toolDeclarationsMaterial,
562
+ ...toolDeclarationPreludeMaterials,
563
+ ...skillIndexMaterials,
529
564
  ...projectContextGovernanceMaterials,
530
565
  ...sessionSummaryMaterial,
531
566
  ...memoryContextMaterials,
@@ -90,7 +90,7 @@ export type McpPlusProfileProposal = {
90
90
  title: string;
91
91
  summary: string;
92
92
  }[];
93
- rationale?: string;
93
+ rationale?: string | Readonly<Record<string, string>>;
94
94
  metadata?: Readonly<Record<string, unknown>>;
95
95
  };
96
96
  export type McpPlusLearnedProfile = {
@@ -99,7 +99,7 @@ export type McpPlusLearnedProfile = {
99
99
  projectId: string;
100
100
  exposure: NonNullable<McpPlusManifest["exposure"]>;
101
101
  skills?: NonNullable<McpPlusManifest["skills"]>;
102
- rationale?: string;
102
+ rationale?: string | Readonly<Record<string, string>>;
103
103
  createdAt: string;
104
104
  updatedAt: string;
105
105
  metadata?: Readonly<Record<string, unknown>>;
@@ -107,14 +107,26 @@ export type McpPlusLearnedProfile = {
107
107
  export type McpPlusRuntimeOverlay = {
108
108
  serverId: string;
109
109
  sessionId: string;
110
- mode: ExposureMode;
111
- activeTools: readonly string[];
112
- pendingReprofile?: boolean;
113
- counters: {
114
- consecutiveIndexedToolCalls: Readonly<Record<string, number>>;
110
+ state: {
111
+ mode: ExposureMode;
112
+ activeTools: readonly string[];
113
+ pendingReprofile?: boolean;
114
+ counters: {
115
+ consecutiveIndexedToolCalls: Readonly<Record<string, number>>;
116
+ };
115
117
  };
116
118
  updatedAt: string;
117
119
  metadata?: Readonly<Record<string, unknown>>;
120
+ /** @deprecated Read-only migration support for pre-contract Praxis overlays. */
121
+ mode?: ExposureMode;
122
+ /** @deprecated Read-only migration support for pre-contract Praxis overlays. */
123
+ activeTools?: readonly string[];
124
+ /** @deprecated Read-only migration support for pre-contract Praxis overlays. */
125
+ pendingReprofile?: boolean;
126
+ /** @deprecated Read-only migration support for pre-contract Praxis overlays. */
127
+ counters?: {
128
+ consecutiveIndexedToolCalls: Readonly<Record<string, number>>;
129
+ };
118
130
  };
119
131
  export type McpPlusProfileStoreKey = {
120
132
  serverId: string;
@@ -206,6 +218,7 @@ export declare function learnedProfileFromProposal(input: {
206
218
  projectId: string;
207
219
  now: string;
208
220
  existing?: McpPlusLearnedProfile;
221
+ protectedAlwaysIndexTools?: readonly string[];
209
222
  }): {
210
223
  ok: true;
211
224
  profile: McpPlusLearnedProfile;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { mkdir, readFile, writeFile } from "node:fs/promises";
7
7
  import path from "node:path";
8
- import { compileMcpPlusManifest, createExpandToolDeclaration, lowerExposurePlanToMcpSurface, planExposure, } from "@praxis-ai/mcp-plus";
8
+ import { compileMcpPlusManifest, createExpandToolDeclaration, createInitToolDeclaration as createMcpPlusContractInitToolDeclaration, createLearnedProfileFromProposal as createContractLearnedProfileFromProposal, createReprofileToolDeclaration as createMcpPlusContractReprofileToolDeclaration, lowerExposurePlanToMcpSurface, mergeMcpPlusPolicy, normalizeProfileProposal, planExposure, validateProfileProposal, } from "@praxis-ai/mcp-plus";
9
9
  export function mcpServer(serverId, input) {
10
10
  return {
11
11
  ...input,
@@ -42,6 +42,14 @@ export const mcp = {
42
42
  toolId: "mcp.resources",
43
43
  metadata: { source: "praxis.mcp.recommendedTools", toolProviderKind: "mcp-static" },
44
44
  },
45
+ {
46
+ toolId: "mcp.prompts",
47
+ metadata: { source: "praxis.mcp.recommendedTools", toolProviderKind: "mcp-static" },
48
+ },
49
+ {
50
+ toolId: "mcp.completions",
51
+ metadata: { source: "praxis.mcp.recommendedTools", toolProviderKind: "mcp-static" },
52
+ },
45
53
  ];
46
54
  },
47
55
  };
@@ -113,18 +121,10 @@ function jsonObjectSchema(properties, required = []) {
113
121
  };
114
122
  }
115
123
  export function createMcpPlusInitToolDeclaration() {
116
- return {
117
- name: "mcp_plus.init",
118
- description: "Submit the first MCP+ profile proposal for this standard MCP server after inspecting its full MCP tools/list surface.",
119
- inputSchema: profileProposalInputSchema("init"),
120
- };
124
+ return createMcpPlusContractInitToolDeclaration();
121
125
  }
122
126
  export function createMcpPlusReprofileToolDeclaration() {
123
- return {
124
- name: "mcp_plus.reprofile",
125
- description: "Submit an updated MCP+ profile proposal for this standard MCP server when runtime usage shows the current profile is stale.",
126
- inputSchema: profileProposalInputSchema("reprofile"),
127
- };
127
+ return createMcpPlusContractReprofileToolDeclaration();
128
128
  }
129
129
  export function createMcpPlusSkillReadToolDeclaration() {
130
130
  return {
@@ -165,36 +165,6 @@ export function createMcpPlusFinishToolDeclaration() {
165
165
  }, ["serverId", "outcome"]),
166
166
  };
167
167
  }
168
- function profileProposalInputSchema(kind) {
169
- return jsonObjectSchema({
170
- serverId: { type: "string" },
171
- pinnedTools: { type: "array", items: { type: "string" } },
172
- warmTools: { type: "array", items: { type: "string" } },
173
- indexedTools: { type: "array", items: { type: "string" } },
174
- alwaysIndexTools: { type: "array", items: { type: "string" } },
175
- toolCards: {
176
- type: "object",
177
- additionalProperties: {
178
- type: "object",
179
- properties: {
180
- title: { type: "string" },
181
- summary: { type: "string" },
182
- keywords: { type: "array", items: { type: "string" } },
183
- },
184
- additionalProperties: false,
185
- },
186
- },
187
- skillChapters: {
188
- type: "array",
189
- items: jsonObjectSchema({
190
- id: { type: "string" },
191
- title: { type: "string" },
192
- summary: { type: "string" },
193
- }, ["id", "title", "summary"]),
194
- },
195
- rationale: { type: "string", description: `${kind} rationale for the host runtime.` },
196
- }, ["serverId"]);
197
- }
198
168
  function dynamicToolSpecForNativeTool(serverId, tool) {
199
169
  return {
200
170
  toolId: `mcp.${toolIdPart(serverId)}.${toolIdPart(tool.name)}`,
@@ -362,6 +332,47 @@ function defaultToolCard(tool) {
362
332
  keywords: tool.name.split(/[^a-zA-Z0-9]+/u).filter(Boolean),
363
333
  };
364
334
  }
335
+ function profileRationaleForContract(rationale) {
336
+ if (typeof rationale === "string") {
337
+ return rationale.trim().length === 0 ? undefined : { summary: rationale };
338
+ }
339
+ if (rationale === undefined)
340
+ return undefined;
341
+ return Object.fromEntries(Object.entries(rationale).filter(([, value]) => typeof value === "string"));
342
+ }
343
+ function toContractProfileProposal(proposal) {
344
+ const toolCards = Object.fromEntries(Object.entries(proposal.toolCards ?? {}).map(([toolName, card]) => [toolName, {
345
+ title: card.title,
346
+ summary: card.summary,
347
+ keywords: card.keywords === undefined ? undefined : [...card.keywords],
348
+ }]));
349
+ const contractProposal = {
350
+ serverId: proposal.serverId,
351
+ pinnedTools: uniqueStrings(proposal.pinnedTools),
352
+ warmTools: proposal.warmTools === undefined ? undefined : uniqueStrings(proposal.warmTools),
353
+ indexedTools: uniqueStrings(proposal.indexedTools),
354
+ alwaysIndexTools: proposal.alwaysIndexTools === undefined ? undefined : uniqueStrings(proposal.alwaysIndexTools),
355
+ toolCards,
356
+ skillChapters: proposal.skillChapters?.map((chapter) => ({ ...chapter })),
357
+ rationale: profileRationaleForContract(proposal.rationale),
358
+ };
359
+ if (Object.hasOwn(proposal, "modeHint")) {
360
+ contractProposal.modeHint = proposal.modeHint;
361
+ }
362
+ return Object.fromEntries(Object.entries(contractProposal).filter(([, value]) => value !== undefined));
363
+ }
364
+ function profileErrorCodeFromValidationIssue(code) {
365
+ switch (code) {
366
+ case "unknown_tool":
367
+ return "MCP_PLUS_PROFILE_UNKNOWN_TOOL";
368
+ case "always_index_pinned":
369
+ return "MCP_PLUS_PROFILE_ALWAYS_INDEX_PINNED";
370
+ case "reserved_runtime_field":
371
+ return "MCP_PLUS_PROFILE_MODE_HINT_UNSUPPORTED";
372
+ default:
373
+ return "MCP_PLUS_PROFILE_INVALID_PROPOSAL";
374
+ }
375
+ }
365
376
  function learnedManifestFromProfile(profile, server) {
366
377
  return {
367
378
  server: {
@@ -373,6 +384,28 @@ function learnedManifestFromProfile(profile, server) {
373
384
  skills: profile.skills,
374
385
  };
375
386
  }
387
+ function contractLearnedProfileFromPraxisProfile(profile) {
388
+ const toolCards = Object.fromEntries(Object.entries(profile.exposure.toolCards ?? {}).flatMap(([toolName, card]) => {
389
+ if (typeof card.summary !== "string" || card.summary.trim().length === 0)
390
+ return [];
391
+ return [[toolName, {
392
+ title: card.title,
393
+ summary: card.summary,
394
+ keywords: card.keywords === undefined ? undefined : [...card.keywords],
395
+ }]];
396
+ }));
397
+ return {
398
+ schemaVersion: profile.schemaVersion,
399
+ serverId: profile.serverId,
400
+ pinnedTools: [...(profile.exposure.pinnedTools ?? [])],
401
+ warmTools: [...(profile.exposure.warmTools ?? [])],
402
+ indexedTools: [...(profile.exposure.indexedTools ?? [])],
403
+ alwaysIndexTools: [...(profile.exposure.alwaysIndexTools ?? [])],
404
+ toolCards,
405
+ skillChapters: profile.skills?.chapters?.map((chapter) => ({ ...chapter })),
406
+ rationale: profileRationaleForContract(profile.rationale),
407
+ };
408
+ }
376
409
  function fallbackMcpPlusManifest(server, nativeTools) {
377
410
  const smallServer = nativeTools.length <= 8;
378
411
  return {
@@ -412,65 +445,41 @@ function bootstrapSurface(server, nativeTools) {
412
445
  };
413
446
  }
414
447
  export function learnedProfileFromProposal(input) {
415
- if (Object.hasOwn(input.proposal, "modeHint")) {
416
- return {
417
- ok: false,
418
- error: {
419
- code: "MCP_PLUS_PROFILE_MODE_HINT_UNSUPPORTED",
420
- message: "MCP+ profile proposals do not accept modeHint in v1; runtime overlays own exposure mode.",
421
- publicSafe: true,
422
- },
423
- };
424
- }
425
- const nativeToolNames = new Set(input.nativeTools.map((tool) => tool.name));
426
- const referenced = [
427
- ...uniqueStrings(input.proposal.pinnedTools),
428
- ...uniqueStrings(input.proposal.warmTools),
429
- ...uniqueStrings(input.proposal.indexedTools),
430
- ...uniqueStrings(input.proposal.alwaysIndexTools),
431
- ...Object.keys(input.proposal.toolCards ?? {}),
432
- ];
433
- const unknown = referenced.filter((toolName) => !nativeToolNames.has(toolName));
434
- if (unknown.length > 0) {
435
- return {
436
- ok: false,
437
- error: {
438
- code: "MCP_PLUS_PROFILE_UNKNOWN_TOOL",
439
- message: `MCP+ profile proposal references unknown tool(s): ${unknown.join(", ")}`,
440
- publicSafe: true,
441
- },
442
- };
443
- }
444
- const alwaysIndex = new Set(uniqueStrings(input.proposal.alwaysIndexTools));
445
- const pinnedAlwaysIndex = uniqueStrings(input.proposal.pinnedTools).filter((toolName) => alwaysIndex.has(toolName));
446
- if (pinnedAlwaysIndex.length > 0) {
448
+ const contractProposal = toContractProfileProposal(input.proposal);
449
+ const validation = validateProfileProposal(contractProposal, input.nativeTools, {
450
+ serverId: input.proposal.serverId,
451
+ alwaysIndexTools: [
452
+ ...(input.existing?.exposure.alwaysIndexTools ?? []),
453
+ ...(input.protectedAlwaysIndexTools ?? []),
454
+ ],
455
+ });
456
+ if (!validation.valid) {
457
+ const primary = validation.issues[0];
447
458
  return {
448
459
  ok: false,
449
460
  error: {
450
- code: "MCP_PLUS_PROFILE_ALWAYS_INDEX_PINNED",
451
- message: `alwaysIndexTools cannot be pinned: ${pinnedAlwaysIndex.join(", ")}`,
461
+ code: primary === undefined ? "MCP_PLUS_PROFILE_INVALID_PROPOSAL" : profileErrorCodeFromValidationIssue(primary.code),
462
+ message: validation.issues.map((issue) => issue.message).join(" "),
452
463
  publicSafe: true,
453
464
  },
454
465
  };
455
466
  }
467
+ const normalizedProposal = normalizeProfileProposal(contractProposal);
468
+ const contractProfile = createContractLearnedProfileFromProposal(normalizedProposal);
456
469
  const profile = {
457
470
  schemaVersion: "mcp-plus.profile.v1",
458
- serverId: input.proposal.serverId,
471
+ serverId: contractProfile.serverId,
459
472
  projectId: input.projectId,
460
473
  exposure: {
461
- pinnedTools: uniqueStrings(input.proposal.pinnedTools),
462
- warmTools: uniqueStrings(input.proposal.warmTools),
463
- indexedTools: uniqueStrings(input.proposal.indexedTools),
464
- alwaysIndexTools: uniqueStrings(input.proposal.alwaysIndexTools),
465
- toolCards: input.proposal.toolCards === undefined ? undefined : Object.fromEntries(Object.entries(input.proposal.toolCards).map(([toolName, card]) => [toolName, {
466
- title: card.title,
467
- summary: card.summary,
468
- keywords: card.keywords === undefined ? undefined : [...card.keywords],
469
- }])),
474
+ pinnedTools: contractProfile.pinnedTools ?? [],
475
+ warmTools: contractProfile.warmTools ?? [],
476
+ indexedTools: contractProfile.indexedTools ?? [],
477
+ alwaysIndexTools: contractProfile.alwaysIndexTools ?? [],
478
+ toolCards: contractProfile.toolCards,
470
479
  },
471
- skills: input.proposal.skillChapters === undefined ? input.existing?.skills : {
480
+ skills: contractProfile.skillChapters === undefined ? input.existing?.skills : {
472
481
  ...(input.existing?.skills ?? {}),
473
- chapters: input.proposal.skillChapters.map((chapter) => ({ ...chapter })),
482
+ chapters: contractProfile.skillChapters.map((chapter) => ({ ...chapter })),
474
483
  },
475
484
  rationale: input.proposal.rationale,
476
485
  createdAt: input.existing?.createdAt ?? input.now,
@@ -514,9 +523,15 @@ export function planMcpHarnessExposure(manifest, nativeToolInventoryByServerId,
514
523
  dynamicToolSpecs: dynamicToolSpecsForSurface(server.serverId, surface),
515
524
  };
516
525
  }
517
- const effectiveManifest = server.manifest ?? (learnedProfile === undefined
526
+ const baseManifest = server.manifest ?? (learnedProfile === undefined
518
527
  ? fallbackMcpPlusManifest(server, nativeTools)
519
528
  : learnedManifestFromProfile(learnedProfile, server));
529
+ const effectiveManifest = learnedProfile === undefined
530
+ ? baseManifest
531
+ : mergeMcpPlusPolicy({
532
+ manifest: baseManifest,
533
+ learnedProfile: contractLearnedProfileFromPraxisProfile(learnedProfile),
534
+ });
520
535
  const graph = compileMcpPlusManifest(effectiveManifest, nativeTools);
521
536
  const state = {
522
537
  serverId: server.serverId,
@@ -525,10 +540,11 @@ export function planMcpHarnessExposure(manifest, nativeToolInventoryByServerId,
525
540
  };
526
541
  const plan = planExposure(graph, state);
527
542
  const surface = lowerExposurePlanToMcpSurface(plan);
543
+ const shouldExposeExpandControl = state.mode === "frozen" || surface.sidecar.toolIndex.length > 0;
528
544
  const withNativeControls = {
529
545
  tools: [
530
546
  ...surface.tools.filter((tool) => tool.name !== "mcp_plus.expand"),
531
- createExpandToolDeclaration(),
547
+ ...(shouldExposeExpandControl ? [createExpandToolDeclaration()] : []),
532
548
  ...(stateByServerId[server.serverId]?.mode === "frozen" ? [] : [
533
549
  createMcpPlusSkillReadToolDeclaration(),
534
550
  createMcpPlusSkillWriteToolDeclaration(),
@@ -0,0 +1,119 @@
1
+ import type { PromptPackMaterialDraft } from "../../executionEngine/promptPack/promptDefiner.js";
2
+ export type SkillPlaneScope = "agent" | "project" | "workspace" | "user" | "session";
3
+ export type SkillPlanePromotionState = "experience" | "skill" | "candidate-mcp-plus" | "mcp-plus" | "tool";
4
+ export type SkillHead = {
5
+ skillId: string;
6
+ title: string;
7
+ summary: string;
8
+ scope?: SkillPlaneScope;
9
+ whenToUse?: string;
10
+ why?: string;
11
+ keywords?: readonly string[];
12
+ pitfallsPreview?: readonly string[];
13
+ bodyRef?: string;
14
+ promotedFrom?: readonly string[];
15
+ promotionState?: SkillPlanePromotionState;
16
+ };
17
+ export type SkillBody = SkillHead & {
18
+ prerequisites?: readonly string[];
19
+ do?: readonly string[];
20
+ avoid?: readonly string[];
21
+ pitfalls?: readonly string[];
22
+ verification?: readonly string[];
23
+ examples?: readonly string[];
24
+ promotionSignals?: readonly string[];
25
+ updatedAt: string;
26
+ };
27
+ export type SkillSourceSpec = {
28
+ kind: "directory";
29
+ path: string;
30
+ scope?: SkillPlaneScope;
31
+ } | {
32
+ kind: "package";
33
+ packageName: string;
34
+ scope?: SkillPlaneScope;
35
+ } | {
36
+ kind: "inline";
37
+ heads: readonly SkillHead[];
38
+ };
39
+ export type SkillPlaneIndexPolicy = {
40
+ maxHeads: number;
41
+ includeScopes: readonly SkillPlaneScope[];
42
+ };
43
+ export type SkillPlaneBodyLoadPolicy = {
44
+ mode: "on-demand" | "eager" | "disabled";
45
+ maxBodiesPerTurn: number;
46
+ };
47
+ export type SkillPlaneLifecyclePolicy = {
48
+ allowWrite: boolean;
49
+ checkpointWrites: boolean;
50
+ promotion: "off" | "suggest" | "auto";
51
+ };
52
+ export type SkillPlaneModuleSpec = {
53
+ kind: "praxis.skill.module";
54
+ version: "praxis.skill.v1";
55
+ sources: readonly SkillSourceSpec[];
56
+ indexPolicy: SkillPlaneIndexPolicy;
57
+ bodyLoadPolicy: SkillPlaneBodyLoadPolicy;
58
+ lifecycle: SkillPlaneLifecyclePolicy;
59
+ metadata?: Readonly<Record<string, unknown>>;
60
+ };
61
+ export type SkillPlaneListHeadsQuery = {
62
+ scopes?: readonly SkillPlaneScope[];
63
+ };
64
+ export type SkillPlaneStore = {
65
+ listHeads(query?: SkillPlaneListHeadsQuery): Promise<readonly SkillHead[]>;
66
+ readBody(skillId: string): Promise<SkillBody | undefined>;
67
+ write(body: SkillBody): Promise<SkillBody>;
68
+ };
69
+ export type SkillPlaneSourceResolutionInput = {
70
+ workspaceRoot?: string;
71
+ };
72
+ export type SkillWriteProposal = {
73
+ kind: "praxis.skill.writeProposal";
74
+ body: SkillBody;
75
+ reason?: string;
76
+ safeForRuntimeInspection: true;
77
+ };
78
+ export type SkillPromotionAdvice = {
79
+ kind: "praxis.skill.promotionAdvice";
80
+ skillId: string;
81
+ from?: SkillPlanePromotionState;
82
+ target: "candidate-mcp-plus";
83
+ autoGenerateTool: false;
84
+ reason?: string;
85
+ signals?: readonly string[];
86
+ };
87
+ export declare const skill: {
88
+ readonly directory: (directoryPath: string, input?: {
89
+ scope?: SkillPlaneScope;
90
+ }) => SkillSourceSpec;
91
+ readonly package: (packageName: string, input?: {
92
+ scope?: SkillPlaneScope;
93
+ }) => SkillSourceSpec;
94
+ readonly inline: (heads: readonly SkillHead[]) => SkillSourceSpec;
95
+ readonly module: (input?: {
96
+ sources?: readonly SkillSourceSpec[];
97
+ indexPolicy?: Partial<SkillPlaneIndexPolicy>;
98
+ bodyLoadPolicy?: Partial<SkillPlaneBodyLoadPolicy>;
99
+ lifecycle?: Partial<SkillPlaneLifecyclePolicy>;
100
+ metadata?: Readonly<Record<string, unknown>>;
101
+ }) => SkillPlaneModuleSpec;
102
+ };
103
+ export declare function isSkillPlaneModuleSpec(value: unknown): value is SkillPlaneModuleSpec;
104
+ export declare function skillPlaneModuleFrom(input: {
105
+ modules?: Readonly<Record<string, unknown>>;
106
+ }): SkillPlaneModuleSpec | undefined;
107
+ export declare function runtimeRequirementsForSkillModule(module: SkillPlaneModuleSpec | undefined): readonly string[];
108
+ export declare function loadSkillHeadsFromSource(source: SkillSourceSpec, input?: SkillPlaneSourceResolutionInput): Promise<readonly SkillHead[]>;
109
+ export declare function loadSkillHeadsFromSources(sources: readonly SkillSourceSpec[], input?: SkillPlaneSourceResolutionInput): Promise<readonly SkillHead[]>;
110
+ export declare function createInMemorySkillPlaneStore(initialBodies?: readonly SkillBody[]): SkillPlaneStore;
111
+ export declare function createFileSkillPlaneStore(rootDir: string): SkillPlaneStore;
112
+ export declare function renderSkillIndexMaterial(heads: readonly SkillHead[]): PromptPackMaterialDraft;
113
+ export declare function createSkillWriteProposal(body: SkillBody, input?: {
114
+ reason?: string;
115
+ }): SkillWriteProposal;
116
+ export declare function adviseSkillPromotion(skillHead: SkillHead, input?: {
117
+ reason?: string;
118
+ signals?: readonly string[];
119
+ }): SkillPromotionAdvice;