@instruktlabs/kiln 1.0.0 → 1.2.0

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 (92) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/CHANGELOG.md +60 -3
  5. package/CONTRIBUTING.md +16 -7
  6. package/README.md +95 -381
  7. package/dist/agent-run.mjs +220 -150
  8. package/dist/build.json +40 -28
  9. package/dist/cli.mjs +242 -163
  10. package/dist/mcp-engine.mjs +412 -154
  11. package/dist/mcp-server.mjs +1 -1
  12. package/docs/architecture.md +6 -3
  13. package/docs/chatgpt.md +20 -5
  14. package/docs/examples.md +40 -170
  15. package/docs/install.md +100 -35
  16. package/docs/migration.md +20 -0
  17. package/docs/sdk.md +49 -5
  18. package/docs/tools-operations.md +1529 -0
  19. package/lib/asset-widget-uri.d.ts +9 -0
  20. package/lib/asset-widget-uri.js +9 -0
  21. package/lib/cli-render-mode.js +10 -3
  22. package/lib/discovery/index.d.ts +1 -1
  23. package/lib/discovery/index.js +2 -2
  24. package/lib/discovery/portable.d.ts +9 -0
  25. package/lib/discovery/portable.js +6 -0
  26. package/lib/discovery/service.d.ts +3 -1
  27. package/lib/discovery/service.js +4 -2
  28. package/lib/engine-identity.d.ts +1 -1
  29. package/lib/engine-identity.js +1 -1
  30. package/lib/evaluator/isolation.js +19 -5
  31. package/lib/evaluator/probe-invariants.d.ts +11 -0
  32. package/lib/evaluator/probe-invariants.js +36 -0
  33. package/lib/evaluator/probe-worker.js +16 -4
  34. package/lib/loopback.d.ts +6 -0
  35. package/lib/loopback.js +6 -0
  36. package/lib/mcp-core.d.ts +210 -0
  37. package/lib/mcp-core.js +279 -0
  38. package/lib/mcp-engine.d.ts +59 -0
  39. package/lib/mcp-engine.js +289 -0
  40. package/lib/mcp-manifest.d.ts +15 -0
  41. package/lib/mcp-manifest.js +65 -0
  42. package/lib/mcp.d.ts +5 -0
  43. package/lib/mcp.js +2 -0
  44. package/lib/render-service-host.js +1 -1
  45. package/lib/tools/discovery.d.ts +1 -1
  46. package/lib/tools/discovery.js +11 -9
  47. package/lib/tools/materials.d.ts +1 -1
  48. package/lib/tools/materials.js +44 -14
  49. package/lib/tools/operation-contract.d.ts +8 -0
  50. package/lib/tools/operation-contract.js +37 -0
  51. package/lib/tools/operations.d.ts +3 -0
  52. package/lib/tools/operations.js +154 -0
  53. package/lib/tools/registry.d.ts +6 -2
  54. package/lib/tools/registry.js +71 -23
  55. package/package.json +23 -1
  56. package/plugin.json +1 -1
  57. package/plugins/kiln-engine/.claude-plugin/icon.png +0 -0
  58. package/plugins/kiln-engine/.claude-plugin/plugin.json +9 -3
  59. package/plugins/kiln-engine/PRIVACY.md +100 -0
  60. package/plugins/kiln-engine/README.md +103 -18
  61. package/plugins/kiln-engine/{scripts/setup-workspace.mjs → bin/kiln-setup-workspace.mjs} +37 -9
  62. package/plugins/kiln-engine/package-provenance.json +15 -8
  63. package/plugins/kiln-engine/plugin.json +10 -4
  64. package/plugins/kiln-engine/runtime.json +10 -1
  65. package/plugins/kiln-engine/skills/kiln-setup-workspace/SKILL.md +17 -18
  66. package/plugins/kiln-engine/skills/kiln-setup-workspace/references/plugin-install.md +28 -8
  67. package/plugins/kiln-engine/skills/kiln-setup-workspace/references/renderer-setup.md +11 -0
  68. package/scripts/create-workspace.mjs +71 -13
  69. package/scripts/workspace-config.mjs +239 -0
  70. package/scripts/workspace-project.mjs +384 -0
  71. package/scripts/workspace-transaction.mjs +504 -0
  72. package/skills/kiln-setup-workspace/SKILL.md +17 -18
  73. package/skills/kiln-setup-workspace/references/plugin-install.md +28 -8
  74. package/skills/kiln-setup-workspace/references/renderer-setup.md +11 -0
  75. package/src/cli-render-mode.ts +9 -9
  76. package/src/cli.ts +3 -3
  77. package/src/discovery/index.ts +2 -0
  78. package/src/discovery/portable.ts +9 -0
  79. package/src/discovery/service.ts +3 -1
  80. package/src/engine-identity.ts +1 -1
  81. package/src/evaluator/isolation.ts +27 -6
  82. package/src/evaluator/probe-invariants.ts +50 -0
  83. package/src/evaluator/probe-worker.ts +22 -4
  84. package/src/mcp-core.ts +3 -0
  85. package/src/mcp-engine.ts +11 -1
  86. package/src/mcp.ts +5 -0
  87. package/src/render-service-host.ts +1 -1
  88. package/src/tools/discovery.ts +20 -10
  89. package/src/tools/materials.ts +59 -15
  90. package/src/tools/operation-contract.ts +51 -0
  91. package/src/tools/operations.ts +177 -0
  92. package/src/tools/registry.ts +100 -20
@@ -28525,7 +28525,7 @@ function isolatedWorkerUrls(moduleUrl) {
28525
28525
  }
28526
28526
 
28527
28527
  // src/evaluator/isolation.ts
28528
- import { existsSync as existsSync3 } from "node:fs";
28528
+ import { existsSync as existsSync3, readlinkSync } from "node:fs";
28529
28529
  import { posix } from "node:path";
28530
28530
  import { fileURLToPath as fileURLToPath3 } from "node:url";
28531
28531
  function isolationUnavailable() {
@@ -28567,7 +28567,7 @@ function isolatedEvaluatorLaunchWithLoader(workerPath, host = {}, loader = "tsx"
28567
28567
  const setprivPath = requiredAbsolutePath(host.setprivPath ?? DEFAULT_SETPRIV_PATH);
28568
28568
  const prlimitPath = requiredAbsolutePath(host.prlimitPath ?? DEFAULT_PRLIMIT_PATH);
28569
28569
  const nodePath = requiredAbsolutePath(host.nodePath ?? DEFAULT_NODE_PATH);
28570
- for (const executable of [bwrapPath, setprivPath, prlimitPath, nodePath]) {
28570
+ for (const executable of [bwrapPath, setprivPath, prlimitPath, nodePath, ENV_PATH]) {
28571
28571
  if (!pathExists(executable))
28572
28572
  isolationUnavailable();
28573
28573
  }
@@ -28603,11 +28603,11 @@ function isolatedEvaluatorLaunchWithLoader(workerPath, host = {}, loader = "tsx"
28603
28603
  for (const mount of runtimeMounts(runtimeRoot, pathExists)) {
28604
28604
  bwrapArgs.push("--ro-bind", mount, mount);
28605
28605
  }
28606
- const nodeArgs = [nodePath, "--max-old-space-size=512", "--disable-proto=throw"];
28606
+ const nodeArgs = [nodePath, "--max-old-space-size=512", "--disable-proto=delete"];
28607
28607
  if (loader === "tsx")
28608
28608
  nodeArgs.push("--import", "tsx");
28609
28609
  nodeArgs.push(resolvedWorkerPath);
28610
- bwrapArgs.push("--chdir", runtimeRoot, "--preserve-fds", "1", "--", prlimitPath, "--cpu=65:65", "--as=6442450944:6442450944", "--fsize=100663296:100663296", "--nofile=64:64", "--nproc=64:64", "--", ...nodeArgs);
28610
+ bwrapArgs.push("--chdir", runtimeRoot, "--", prlimitPath, "--cpu=65:65", "--as=6442450944:6442450944", "--fsize=100663296:100663296", "--nofile=64:64", "--nproc=64:64", "--", ENV_PATH, "-i", "NODE_ENV=production", "NO_COLOR=1", ...nodeArgs);
28611
28611
  return {
28612
28612
  command: setprivPath,
28613
28613
  args: [
@@ -28631,7 +28631,7 @@ async function renderGLBViaIsolatedEvaluator(code, options = {}, controls = {})
28631
28631
  const { host: _, ...processControls } = controls;
28632
28632
  return renderGLBViaProcessLaunch(code, options, processControls, launch);
28633
28633
  }
28634
- var DEFAULT_RUNTIME_ROOT = "/app", DEFAULT_BWRAP_PATH = "/usr/local/bin/bwrap", DEFAULT_SETPRIV_PATH = "/usr/bin/setpriv", DEFAULT_PRLIMIT_PATH = "/usr/bin/prlimit", DEFAULT_NODE_PATH = "/usr/local/bin/node", MAX_READINESS_PROTOCOL_BYTES;
28634
+ var DEFAULT_RUNTIME_ROOT = "/app", DEFAULT_BWRAP_PATH = "/usr/local/bin/bwrap", DEFAULT_SETPRIV_PATH = "/usr/bin/setpriv", DEFAULT_PRLIMIT_PATH = "/usr/bin/prlimit", DEFAULT_NODE_PATH = "/usr/local/bin/node", ENV_PATH = "/usr/bin/env", MAX_READINESS_PROTOCOL_BYTES;
28635
28635
  var init_isolation = __esm(() => {
28636
28636
  init_subprocess();
28637
28637
  MAX_READINESS_PROTOCOL_BYTES = 16 * 1024;
@@ -33769,8 +33769,7 @@ async function startLocalRenderService(dir = renderServiceDir(), environment = p
33769
33769
  if (exited)
33770
33770
  exitedAt ??= Date.now();
33771
33771
  if (spawnError || exitedAt !== undefined && Date.now() - exitedAt >= 1500)
33772
- throw new Error(`render service exited during startup${spawnError ? `: ${spawnError.message}` : ""}${stderr.trim() ? `: ${stderr.trim().split(`
33773
- `).slice(-3).join(" ")}` : windowsLaunch ? "; launch the renderer manually to inspect native-driver errors" : ""}`);
33772
+ throw new Error(`render service exited during startup${spawnError ? `: ${spawnError.message}` : ""}${stderr.trim() ? `: ${stderr.trim()}` : windowsLaunch ? "; launch the renderer manually to inspect native-driver errors" : ""}`);
33774
33773
  await new Promise((done) => setTimeout(done, 250));
33775
33774
  }
33776
33775
  throw new Error(`render service health is unknown after ${budget}ms; inspect with \`kiln service reprobe\``);
@@ -33889,12 +33888,17 @@ function makeLazyRenderPort(start, token, sourceFingerprint, initialUrl) {
33889
33888
  } catch (error) {
33890
33889
  if (execution?.signal?.aborted || error.name === "TimeoutError")
33891
33890
  throw error;
33892
- if ((await readRenderServiceHealth(url, {
33891
+ if (resolving !== pending)
33892
+ return (await resolve()).port(req, execution);
33893
+ const health = await readRenderServiceHealth(url, {
33893
33894
  token,
33894
33895
  signal: execution?.signal
33895
- })).kind !== "absent")
33896
- throw error;
33896
+ });
33897
33897
  execution?.signal?.throwIfAborted();
33898
+ if (resolving !== pending)
33899
+ return (await resolve()).port(req, execution);
33900
+ if (health.kind !== "absent")
33901
+ throw error;
33898
33902
  if (resolving === pending)
33899
33903
  resolving = undefined;
33900
33904
  return (await resolve()).port(req, execution);
@@ -36102,7 +36106,7 @@ import { readFile as readFile8 } from "node:fs/promises";
36102
36106
  var KILN_ASSET_WIDGET_URI = "ui://kiln/asset-v5.html";
36103
36107
 
36104
36108
  // src/engine-identity.ts
36105
- var ENGINE_VERSION = "1.0.0";
36109
+ var ENGINE_VERSION = "1.2.0";
36106
36110
  var ENGINE_INSTALL_URL = new URL("../", import.meta.url).href;
36107
36111
  function engineIdentity() {
36108
36112
  return { version: ENGINE_VERSION, installUrl: ENGINE_INSTALL_URL };
@@ -36113,6 +36117,7 @@ init_requirements_json();
36113
36117
  var MCP_SERVER_NAME = "kiln";
36114
36118
  var MCP_SERVER_VERSION = ENGINE_VERSION;
36115
36119
  var MCP_SERVER_INSTRUCTIONS = "Kiln turns JavaScript you write into GLB assets and returns rendered views. Work by reference: send a program once to kiln_validate or kiln_render, keep the returned programRef exactly, and use it for every later render, inspect, kiln_source and kiln_edit call; never resend a program to change part of it. Call kiln_discover first for helper contracts, with capabilities:true for the host's runtime, project and camera facts. Read viewFidelity before judging materials: a CPU view shows shape, not material; GPU views need the render service in render-service/. Agent Skills for authoring, refining, QA and scenes ship in skills/ beside this server's dist/; read the matching SKILL.md first.";
36120
+ var MCP_OPERATION_INSTRUCTIONS = "Kiln turns authored JavaScript into editable GLB assets and rendered views. Send source once to kiln_validate or kiln_render, then preserve the exact programRef for later reads, edits and renders. Use kiln_discover for helper contracts and full input shapes; use kiln_capabilities for host configuration and limits. Read viewFidelity before judging materials: CPU geometry views are not material evidence. Search saved assets with kiln_assets_search; reopen exact source with kiln_assets_restore. Browse materials with kiln_material_search and create with kiln_material_create. Save only when requested. Pin collection, asset and revision IDs for reuse and export.";
36116
36121
  var KILN_WIDGET_META = {
36117
36122
  ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: [] } },
36118
36123
  "openai/widgetDescription": "Inspect the saved 3D asset and download its GLB or editable bundle.",
@@ -36239,7 +36244,7 @@ var PRE_HANDSHAKE_METHODS = new Set(["initialize", "ping"]);
36239
36244
  // src/tools/registry.ts
36240
36245
  init_capture_cache();
36241
36246
  init_assets();
36242
- import { z as z21 } from "zod";
36247
+ import { z as z23 } from "zod";
36243
36248
 
36244
36249
  // src/requirements-assets.ts
36245
36250
  init_requirements_context();
@@ -40396,7 +40401,7 @@ var MAX_RESPONSE_BYTES = 64 * 1024;
40396
40401
  var MAX_TEXT_CHARS = 16 * 1024;
40397
40402
  var DETAIL_RESULT_BUDGET = 15000;
40398
40403
  var compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
40399
- function createDiscoveryService(source, index, capabilities, retirements = {}, notes = async () => []) {
40404
+ function createDiscoveryService(source, index, capabilities, retirements = {}, notes = async () => [], capabilitiesCall = "capabilities:true") {
40400
40405
  const entries = parseCatalog(source);
40401
40406
  const byId = new Map(entries.map((entry) => [entry.id, entry]));
40402
40407
  const byName = new Map(entries.filter((entry) => entry.kind !== "recipe").map((entry) => [entry.name, entry]));
@@ -40419,7 +40424,7 @@ function createDiscoveryService(source, index, capabilities, retirements = {}, n
40419
40424
  "Parts auto-add to opts.parent; do not wrap createPart in parent.add. Part rotation uses degrees.",
40420
40425
  "Custom THREE.BufferGeometry and ordinary JavaScript functions remain available. Recipes offer guidance and never restrict modeling.",
40421
40426
  "Send source once and reuse programRef for reads, edits, inspection and rendering. Review geometry, materials and destination constraints separately.",
40422
- "Search for the operation you need in ordinary language, then fetch exact contracts with ids. Use capabilities:true for the current host."
40427
+ `Search for the operation you need in ordinary language, then fetch exact contracts with ids. Use ${capabilitiesCall} for the current host.`
40423
40428
  ]
40424
40429
  };
40425
40430
  const error = (mode, code, message) => ({
@@ -40600,12 +40605,12 @@ var snapshot2;
40600
40605
  function createDiscovery(capabilities = async () => ({
40601
40606
  host: "No host capability provider was supplied.",
40602
40607
  discovery: { mode: "lexical", offline: true, requiresModel: false }
40603
- }), notes = async () => []) {
40608
+ }), notes = async () => [], capabilitiesCall = "capabilities:true") {
40604
40609
  snapshot2 ??= (() => {
40605
40610
  const entries = listDiscoveryEntries();
40606
40611
  return { entries, index: createLexicalDiscoveryIndex(entries) };
40607
40612
  })();
40608
- return createDiscoveryService(snapshot2.entries, snapshot2.index, capabilities, REMOVED_AUTHORING_HELPERS, notes);
40613
+ return createDiscoveryService(snapshot2.entries, snapshot2.index, capabilities, REMOVED_AUTHORING_HELPERS, notes, capabilitiesCall);
40609
40614
  }
40610
40615
 
40611
40616
  // src/tools/discovery.ts
@@ -40614,8 +40619,8 @@ init_capture_limits();
40614
40619
  init_geometry_export();
40615
40620
  init_community_exporter();
40616
40621
  init_material_resources();
40617
- function createKilnDiscoveryDef(context) {
40618
- const run = createDiscovery(() => currentCapabilities(context), async () => projectNotes(context));
40622
+ function createKilnDiscoveryDef(context, presentation = "grouped") {
40623
+ const run = createDiscovery(() => currentCapabilities(context, presentation), async () => projectNotes(context), presentation === "operations" ? "kiln_capabilities({})" : "capabilities:true");
40619
40624
  return {
40620
40625
  name: "kiln_discover",
40621
40626
  description: "Discover Kiln operations, assemblies, recipes, tool-input shapes and current host capabilities. Omit arguments for a compact overview. Search with ordinary modeling language using query; refine with family, kind or tags. Fetch complete contracts/examples with ids (up to six exact IDs, executable names or shape: ids). Overview/search pages default to six summaries. Recipes guide construction without restricting the asset. Search runs locally without models or network calls.",
@@ -40639,7 +40644,7 @@ function projectNotes(context) {
40639
40644
  `Project ${projectId} is configured for this workspace and applies when projectId is omitted: read its brief, design, inventory and material pins first with kiln_project { action: "get", projectId: "${projectId}" }; projectId: null selects standalone work.`
40640
40645
  ] : [];
40641
40646
  }
40642
- async function currentCapabilities(context) {
40647
+ async function currentCapabilities(context, presentation) {
40643
40648
  const externalEvaluator = Boolean(context.evaluatorPort) || context.evaluatorProfile === "evaluator-required";
40644
40649
  const approvedTextures = context.approvedTextureResources ? context.approvedTextureResources() : externalEvaluator ? null : approvedTextureCatalogV1();
40645
40650
  const textureSlots = approvedTextures === null ? null : {};
@@ -40668,7 +40673,7 @@ async function currentCapabilities(context) {
40668
40673
  requiresModel: false
40669
40674
  },
40670
40675
  engine: engineIdentity(),
40671
- project: {
40676
+ project: presentation === "operations" && !context.projectStore ? { available: false } : {
40672
40677
  configured: configuredProject(context),
40673
40678
  select: "projectId per call; omitted uses the configured project, null selects standalone work",
40674
40679
  read: 'kiln_project { action: "get", projectId } returns the brief, design, inventory and material pins'
@@ -40687,7 +40692,7 @@ async function currentCapabilities(context) {
40687
40692
  available: Boolean(context.assetLibrary),
40688
40693
  collections: context.assetLibrary?.collections() ?? [],
40689
40694
  save: "kiln_save persists exact GLB, source and provenance; draft renders do not populate collections",
40690
- resume: "kiln_assets action=restore imports a saved revision into the current program store",
40695
+ resume: `${presentation === "operations" ? "kiln_assets_restore" : "kiln_assets action=restore"} imports a saved revision into the current program store`,
40691
40696
  downloads: "kiln_export returns GLB/source/ZIP resource links; client presentation varies",
40692
40697
  viewer: "kiln_present opens a saved revision in supporting chat clients with 3D viewing and downloads; kiln view opens a local collection or standalone GLB/ZIP"
40693
40698
  },
@@ -41076,13 +41081,39 @@ var MATERIAL_ACTIONS = [
41076
41081
  "create-procedural",
41077
41082
  "import"
41078
41083
  ];
41079
- var REQUIREMENTS2 = {
41080
- presets: { required: [] },
41081
- "create-preset": { required: ["presetId", "seed", "creator", "license"] },
41082
- list: { required: [] },
41083
- get: { required: ["materialId"] },
41084
- "create-procedural": { required: ["draft"], shapes: { draft: "shape:material-draft" } },
41085
- import: { required: ["payload"], shapes: { payload: "shape:material-import" } }
41084
+ var MATERIAL_CONTRACT = {
41085
+ presets: {
41086
+ fields: ["tag"],
41087
+ required: [],
41088
+ description: "List shipped material recipes, optionally filtered by tag."
41089
+ },
41090
+ "create-preset": {
41091
+ fields: ["presetId", "seed", "creator", "license", "size", "materialId"],
41092
+ required: ["presetId", "seed", "creator", "license"],
41093
+ description: "Create an immutable material from a shipped preset with explicit seed, creator and license."
41094
+ },
41095
+ list: {
41096
+ fields: ["tag"],
41097
+ required: [],
41098
+ description: "List saved immutable material revisions, optionally filtered by tag."
41099
+ },
41100
+ get: {
41101
+ fields: ["materialId", "revisionId"],
41102
+ required: ["materialId"],
41103
+ description: "Read a material revision, provenance and portable specification. Omit revisionId only when exactly one revision exists."
41104
+ },
41105
+ "create-procedural": {
41106
+ fields: ["draft"],
41107
+ required: ["draft"],
41108
+ shapes: { draft: "shape:material-draft" },
41109
+ description: "Create an immutable material from a bounded procedural draft. Does not fetch URLs or execute source."
41110
+ },
41111
+ import: {
41112
+ fields: ["payload"],
41113
+ required: ["payload"],
41114
+ shapes: { payload: "shape:material-import" },
41115
+ description: "Import complete normalized material records and embedded PNGs. Does not fetch URLs or execute source."
41116
+ }
41086
41117
  };
41087
41118
  var record7 = (description) => z18.record(z18.string(), z18.unknown()).optional().describe(description);
41088
41119
  var tag = z18.string().min(1).max(80).optional().describe("presets, list: keep one tag.");
@@ -41104,13 +41135,13 @@ var materialResult = (material) => ({
41104
41135
  material,
41105
41136
  portableSpec: materialLibraryPortableSpec(material)
41106
41137
  });
41107
- var unknownMaterial = (materialId, revisionId) => new Error(`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ""}. kiln_material { action: 'list' } lists the materials in this workspace with their revision IDs.`);
41108
- async function onlyRevision(library, materialId) {
41138
+ var unknownMaterial = (materialId, revisionId, listCall) => new Error(`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ""}. ${listCall} lists the materials in this workspace with their revision IDs.`);
41139
+ async function onlyRevision(library, materialId, listCall) {
41109
41140
  const revisions = (await library.list()).filter((material) => material.materialId === materialId).map((material) => material.revisionId).sort();
41110
41141
  if (revisions.length === 1)
41111
41142
  return revisions[0];
41112
41143
  if (revisions.length === 0)
41113
- throw unknownMaterial(materialId);
41144
+ throw unknownMaterial(materialId, undefined, listCall);
41114
41145
  throw new Error(`Material ${materialId} has ${revisions.length} revisions: ${revisions.join(", ")}. Pass revisionId to get one.`);
41115
41146
  }
41116
41147
  function aliasMaterialId(raw) {
@@ -41123,11 +41154,13 @@ function aliasMaterialId(raw) {
41123
41154
  throw new Error(`kiln_material: resourceId (${String(resourceId)}) and materialId (${String(rest.materialId)}) name different materials; pass materialId alone.`);
41124
41155
  return { ...rest, materialId: resourceId };
41125
41156
  }
41126
- function createKilnMaterialDef(library) {
41157
+ function createKilnMaterialDef(library, presentation = "grouped") {
41158
+ const listCall = presentation === "operations" ? 'kiln_material_search({ scope: "saved" })' : "kiln_material { action: 'list' }";
41127
41159
  return {
41128
41160
  name: "kiln_material",
41129
41161
  description: "Manage optional immutable material resources in this workspace. presets discovers shipped architecture, wood, metal, fabric and ground recipes; create-preset bakes one with an explicit seed, creator and license; list returns compact material/revision summaries; get returns full provenance, hashes, map conventions, physical repeat scale and a code-ready portable material spec; create-procedural bakes bounded editable layer recipes including optional height-derived normals; import accepts complete normalized records with embedded PNG bytes. No action downloads URLs or executes source. Pin the returned materialId/revisionId through per-invocation materialDependencies or project dependencies before authored evaluation resolves the resources; no project is required.",
41130
41162
  inputSchema: materialToolInput,
41163
+ actionContract: MATERIAL_CONTRACT,
41131
41164
  annotations: {
41132
41165
  readOnlyHint: false,
41133
41166
  destructiveHint: false,
@@ -41137,7 +41170,7 @@ function createKilnMaterialDef(library) {
41137
41170
  async run(raw) {
41138
41171
  assertMaterialJson(raw);
41139
41172
  const input = materialToolInput.parse(aliasMaterialId(raw));
41140
- requireActionFields("kiln_material", input.action, input, REQUIREMENTS2[input.action]);
41173
+ requireActionFields("kiln_material", input.action, input, MATERIAL_CONTRACT[input.action]);
41141
41174
  switch (input.action) {
41142
41175
  case "presets":
41143
41176
  return {
@@ -41173,12 +41206,12 @@ function createKilnMaterialDef(library) {
41173
41206
  };
41174
41207
  }
41175
41208
  case "get": {
41176
- const revisionId = input.revisionId ?? await onlyRevision(library, input.materialId);
41209
+ const revisionId = input.revisionId ?? await onlyRevision(library, input.materialId, listCall);
41177
41210
  try {
41178
41211
  return materialResult((await library.read(input.materialId, revisionId)).manifest);
41179
41212
  } catch (error) {
41180
41213
  if (error.code === "ENOENT")
41181
- throw unknownMaterial(input.materialId, revisionId);
41214
+ throw unknownMaterial(input.materialId, revisionId, listCall);
41182
41215
  throw error;
41183
41216
  }
41184
41217
  }
@@ -41229,7 +41262,7 @@ async function persistedPreviewFidelity(fidelity, glb) {
41229
41262
  var operationId = z19.string().regex(/^op_[a-f0-9-]{36}$/);
41230
41263
  var assetId = z19.string().regex(/^[a-z][a-z0-9_-]{0,79}$/);
41231
41264
  var REVIEW_ACTIONS = ["list", "get", "pin", "save"];
41232
- var REQUIREMENTS3 = {
41265
+ var REQUIREMENTS2 = {
41233
41266
  list: { required: [] },
41234
41267
  get: { required: ["operationId"] },
41235
41268
  pin: { required: ["operationId", "pinned"] },
@@ -41311,7 +41344,7 @@ function createKilnReviewDef(context) {
41311
41344
  },
41312
41345
  async run(raw) {
41313
41346
  const input = reviewToolInput.parse(raw);
41314
- requireActionFields("kiln_review", input.action, input, REQUIREMENTS3[input.action]);
41347
+ requireActionFields("kiln_review", input.action, input, REQUIREMENTS2[input.action]);
41315
41348
  if (input.action === "list") {
41316
41349
  const snapshot = await store.snapshot({ projectId: input.projectId });
41317
41350
  const offset = input.offset ?? 0;
@@ -41483,6 +41516,191 @@ function withWorkspaceContext(def, context) {
41483
41516
  };
41484
41517
  }
41485
41518
 
41519
+ // src/tools/operations.ts
41520
+ import { z as z22 } from "zod";
41521
+
41522
+ // src/tools/operation-contract.ts
41523
+ import { z as z21 } from "zod";
41524
+ function projectActionSchemas(name, input, actions) {
41525
+ if (input.def.checks?.length)
41526
+ throw new Error(`${name}: cross-field constraints require an explicit operation schema`);
41527
+ const json = z21.toJSONSchema(input, { io: "input" });
41528
+ const selector = json.properties?.action;
41529
+ const expected = Object.keys(actions).sort();
41530
+ if (!Array.isArray(selector?.enum) || JSON.stringify([...selector.enum].sort()) !== JSON.stringify(expected))
41531
+ throw new Error(`${name}: unclassified actions`);
41532
+ const classified = new Set([
41533
+ "action",
41534
+ ...Object.values(actions).flatMap((contract) => [...contract.fields])
41535
+ ]);
41536
+ const unclassified = Object.keys(input.shape).filter((field) => !classified.has(field));
41537
+ if (unclassified.length)
41538
+ throw new Error(`${name}: Unclassified fields: ${unclassified.join(", ")}`);
41539
+ const schemas = [];
41540
+ for (const [action, contract] of Object.entries(actions)) {
41541
+ for (const field of contract.required)
41542
+ if (!contract.fields.includes(field))
41543
+ throw new Error(`${name}.${action}: unmapped required ${field}`);
41544
+ for (const field of json.required ?? [])
41545
+ if (field !== "action" && !contract.fields.includes(field))
41546
+ throw new Error(`${name}.${action}: would omit required ${field}`);
41547
+ const shape = {};
41548
+ for (const field of contract.fields) {
41549
+ if (field === "action" || !Object.hasOwn(input.shape, field))
41550
+ throw new Error(`${name}.${action}: missing or reserved field ${field}`);
41551
+ classified.add(field);
41552
+ const source = input.shape[field];
41553
+ shape[field] = contract.required.includes(field) ? source.nonoptional() : source;
41554
+ }
41555
+ schemas.push([action, z21.strictObject(shape)]);
41556
+ }
41557
+ return Object.fromEntries(schemas);
41558
+ }
41559
+
41560
+ // src/tools/operations.ts
41561
+ function projectOperationDefs(defs, context) {
41562
+ return defs.flatMap((def) => {
41563
+ if (!context.assetLibrary && ["kiln_save", "kiln_assets", "kiln_present", "kiln_export", "kiln_import"].includes(def.name))
41564
+ return [];
41565
+ if (def.name === "kiln_renderer")
41566
+ return rendererOperations(def, context);
41567
+ if (def.name === "kiln_discover")
41568
+ return discoveryOperations(def);
41569
+ if (def.name === "kiln_material")
41570
+ return materialOperations(def);
41571
+ if (def.name === "kiln_assets")
41572
+ return assetOperations(def, context);
41573
+ return [def];
41574
+ });
41575
+ }
41576
+ function actionTools(def) {
41577
+ if (!(def.inputSchema instanceof z22.ZodObject) || !def.actionContract)
41578
+ throw new Error(`${def.name}: missing shared action contract`);
41579
+ const schemas = projectActionSchemas(def.name, def.inputSchema, def.actionContract);
41580
+ return Object.fromEntries(Object.entries(def.actionContract).map(([action, contract]) => [
41581
+ action,
41582
+ {
41583
+ ...def,
41584
+ actionContract: undefined,
41585
+ name: `${def.name}_${action.replaceAll("-", "_")}`,
41586
+ description: contract.description,
41587
+ inputSchema: schemas[action],
41588
+ run: async (raw) => def.run({ ...schemas[action].parse(raw), action })
41589
+ }
41590
+ ]));
41591
+ }
41592
+ function assetOperations(def, context) {
41593
+ const tools = actionTools(def);
41594
+ const list = tools.list;
41595
+ const listSchema = list.inputSchema;
41596
+ const collection = listSchema.shape.collection.unwrap().optional().describe("Collection ID from kiln_assets_collections; omitted searches all configured collections.");
41597
+ const inputSchema = listSchema.extend({ collection });
41598
+ const search = {
41599
+ ...list,
41600
+ name: "kiln_assets_search",
41601
+ description: "Search saved assets with pagination. Omit collection for all configured collections, or select one explicitly. Results identify exact collection, asset and revision; partial read failures are reported.",
41602
+ inputSchema,
41603
+ run: async (raw) => {
41604
+ const input = inputSchema.parse(raw);
41605
+ const { collection: _collection, ...filters } = input;
41606
+ if (input.collection === undefined)
41607
+ return tools.catalog.run(filters);
41608
+ if (!context.assetLibrary?.collections().some((item) => item.id === input.collection))
41609
+ throw new Error("Unknown collection. Use kiln_assets_collections to list available destinations.");
41610
+ const result = await list.run(input);
41611
+ return {
41612
+ errors: [],
41613
+ total: result.total,
41614
+ nextOffset: result.nextOffset,
41615
+ assets: result.assets.map((asset) => ({ ...asset, collection: input.collection }))
41616
+ };
41617
+ }
41618
+ };
41619
+ return [tools.collections, search, tools.get, tools.restore];
41620
+ }
41621
+ function materialOperations(def) {
41622
+ const tools = actionTools(def);
41623
+ const presets = tools.presets;
41624
+ const inputSchema = presets.inputSchema.extend({
41625
+ scope: z22.enum(["all", "presets", "saved"]).default("all")
41626
+ });
41627
+ const search = {
41628
+ ...presets,
41629
+ name: "kiln_material_search",
41630
+ description: "Browse shipped material recipes and saved material revisions, optionally filtered by exact tag. Recipe IDs and saved material IDs are distinct. Use scope to select presets, saved or both.",
41631
+ inputSchema,
41632
+ run: async (raw) => {
41633
+ const { scope, ...input } = inputSchema.parse(raw);
41634
+ const recipes = scope === "saved" ? { presets: [] } : await presets.run(input);
41635
+ const saved = scope === "presets" ? { materials: [] } : await tools.list.run(input);
41636
+ return {
41637
+ ok: true,
41638
+ presets: recipes.presets.map((item) => ({ ...item, kind: "preset" })),
41639
+ materials: saved.materials.map((item) => ({ ...item, kind: "saved" }))
41640
+ };
41641
+ }
41642
+ };
41643
+ const preset = tools["create-preset"];
41644
+ const procedural = tools["create-procedural"];
41645
+ const createInput = z22.strictObject({
41646
+ definition: z22.discriminatedUnion("kind", [
41647
+ preset.inputSchema.extend({ kind: z22.literal("preset") }),
41648
+ procedural.inputSchema.extend({ kind: z22.literal("procedural") })
41649
+ ])
41650
+ });
41651
+ const create = {
41652
+ ...preset,
41653
+ name: "kiln_material_create",
41654
+ description: "Create and retain one immutable material from a preset or procedural definition. Presets require explicit seed, creator and license. Pin the returned materialId/revisionId for rendering. Does not fetch URLs or execute source.",
41655
+ inputSchema: createInput,
41656
+ run: async (raw) => {
41657
+ const { kind, ...input } = createInput.parse(raw).definition;
41658
+ return (kind === "preset" ? preset : procedural).run(input);
41659
+ }
41660
+ };
41661
+ return [search, tools.get, create, tools.import];
41662
+ }
41663
+ function rendererOperations(def, context) {
41664
+ if (!context.renderCapabilities)
41665
+ return [];
41666
+ return ["status", ...context.reprobeRenderer ? ["reprobe"] : []].map((action) => {
41667
+ const inputSchema = z22.strictObject({});
41668
+ return {
41669
+ ...def,
41670
+ name: `kiln_renderer_${action}`,
41671
+ inputSchema,
41672
+ run: async (raw) => {
41673
+ inputSchema.parse(raw);
41674
+ return def.run({ action });
41675
+ }
41676
+ };
41677
+ });
41678
+ }
41679
+ function discoveryOperations(def) {
41680
+ const original = def.inputSchema;
41681
+ const { capabilities: _capabilities, ...fields } = original.shape;
41682
+ const inputSchema = z22.strictObject(fields);
41683
+ const empty = z22.strictObject({});
41684
+ return [
41685
+ {
41686
+ ...def,
41687
+ description: "Discover geometry helpers, assemblies, recipes and complete input shapes. Search with modeling language or fetch up to six exact IDs. Use kiln_capabilities for current host facts.",
41688
+ inputSchema,
41689
+ run: async (raw) => def.run(inputSchema.parse(raw))
41690
+ },
41691
+ {
41692
+ ...def,
41693
+ name: "kiln_capabilities",
41694
+ description: "Inspect declared host capabilities, execution limits and available storage. Observed rendering fidelity is returned by each render.",
41695
+ inputSchema: empty,
41696
+ run: async (raw) => {
41697
+ empty.parse(raw);
41698
+ return def.run({ capabilities: true });
41699
+ }
41700
+ }
41701
+ ];
41702
+ }
41703
+
41486
41704
  // src/tools/registry.ts
41487
41705
  init_validation();
41488
41706
  init_source_check();
@@ -41948,11 +42166,11 @@ function derivativeReviewFidelity(receipts) {
41948
42166
  ...reasonCodes.length ? { reasonCodes } : {}
41949
42167
  };
41950
42168
  }
41951
- var validateInput = z21.object({
41952
- code: z21.string().describe("Kiln source code (defines `meta` + `build()`, optional `animate()`).")
42169
+ var validateInput = z23.object({
42170
+ code: z23.string().describe("Kiln source code (defines `meta` + `build()`, optional `animate()`).")
41953
42171
  });
41954
- var renderInput = z21.object({
41955
- code: z21.string().describe("Kiln source code to execute and render to an in-memory GLB.")
42172
+ var renderInput = z23.object({
42173
+ code: z23.string().describe("Kiln source code to execute and render to an in-memory GLB.")
41956
42174
  });
41957
42175
  var renderViewsInput = renderInput.extend({ capture: captureInput, detail: reviewDetailInput });
41958
42176
  var reviewDetailOptions = (context) => ({
@@ -41965,26 +42183,26 @@ var reviewDetailOptions = (context) => ({
41965
42183
  }
41966
42184
  });
41967
42185
  var renderViewsBufferInput = renderViewsInput.omit({ code: true });
41968
- var screenshotAnimationInput = z21.object({
42186
+ var screenshotAnimationInput = z23.object({
41969
42187
  shot: cameraShotInput.optional(),
41970
- measureParts: z21.array(cameraShotInput.shape.subject.unwrap()).min(1).max(16).optional().describe("Exact names or paths of subtrees measured together at each phase, independent of camera selection."),
41971
- frames: z21.number().int().min(2).max(6).optional(),
41972
- frameTimes: z21.array(z21.number().min(0).max(1)).min(1).max(9).optional().describe("Ordered phase fractions 0..1; mutually exclusive with frames."),
41973
- framing: z21.enum(["locked", "follow"]).optional(),
41974
- size: z21.number().int().min(128).max(1024).optional().describe("Frame size in px; default 256."),
42188
+ measureParts: z23.array(cameraShotInput.shape.subject.unwrap()).min(1).max(16).optional().describe("Exact names or paths of subtrees measured together at each phase, independent of camera selection."),
42189
+ frames: z23.number().int().min(2).max(6).optional(),
42190
+ frameTimes: z23.array(z23.number().min(0).max(1)).min(1).max(9).optional().describe("Ordered phase fractions 0..1; mutually exclusive with frames."),
42191
+ framing: z23.enum(["locked", "follow"]).optional(),
42192
+ size: z23.number().int().min(128).max(1024).optional().describe("Frame size in px; default 256."),
41975
42193
  detail: reviewDetailInput,
41976
- code: z21.string().describe("Kiln source code to execute; must define animate() returning the named clip."),
41977
- clip: z21.string().describe('The animation clip to view, by name (e.g. "walk", "attack"). Must be one your animate() returns.'),
41978
- camera: z21.string().optional().describe("Camera angle: right (default — side profile, best for leg swing + knee bend direction), front " + "(reveals sideways/lateral motion), back, left, top, or three-quarter."),
41979
- perFrame: z21.boolean().optional().describe("Return the frames as separate high-res images instead of one composite grid. Default false.")
42194
+ code: z23.string().describe("Kiln source code to execute; must define animate() returning the named clip."),
42195
+ clip: z23.string().describe('The animation clip to view, by name (e.g. "walk", "attack"). Must be one your animate() returns.'),
42196
+ camera: z23.string().optional().describe("Camera angle: right (default — side profile, best for leg swing + knee bend direction), front " + "(reveals sideways/lateral motion), back, left, top, or three-quarter."),
42197
+ perFrame: z23.boolean().optional().describe("Return the frames as separate high-res images instead of one composite grid. Default false.")
41980
42198
  });
41981
42199
  var screenshotAnimationAdvertisedInput = screenshotAnimationInput.extend({
41982
42200
  shot: cameraShotRecordInput
41983
42201
  });
41984
- var viewInteriorInput = z21.object({
42202
+ var viewInteriorInput = z23.object({
41985
42203
  capture: advancedCaptureInput.optional(),
41986
- code: z21.string().describe("Kiln source code to execute and render with the roof hidden."),
41987
- nodeName: z21.string().optional().describe("Override: lift the roof by exact node name instead of by role. Matches that node and its " + "children. Normally OMIT it — Kiln finds the roof from its semantic role (anything built " + 'with createRoofPlanes/createGableRoof), falling back to historical "Roof" naming.')
42204
+ code: z23.string().describe("Kiln source code to execute and render with the roof hidden."),
42205
+ nodeName: z23.string().optional().describe("Override: lift the roof by exact node name instead of by role. Matches that node and its " + "children. Normally OMIT it — Kiln finds the roof from its semantic role (anything built " + 'with createRoofPlanes/createGableRoof), falling back to historical "Roof" naming.')
41988
42206
  });
41989
42207
  var viewInteriorAdvertisedInput = viewInteriorInput.extend({ capture: captureRecordInput });
41990
42208
  function runValidate(input, context) {
@@ -41999,11 +42217,11 @@ function runValidate(input, context) {
41999
42217
  warnings: result.warnings.map((w) => w.fixHint ? `${w.message} (${w.fixHint})` : w.message)
42000
42218
  };
42001
42219
  }
42002
- var partListInput = z21.object({
42003
- query: z21.string().max(4096).optional().describe("Case-insensitive substring of name or exact encoded path; not a regex."),
42004
- offset: z21.number().int().min(0).optional(),
42005
- limit: z21.number().int().min(1).max(100).optional(),
42006
- placement: z21.boolean().optional().describe("Add world position, rotation, scale, mirroring and bounds per part; pages of 50.")
42220
+ var partListInput = z23.object({
42221
+ query: z23.string().max(4096).optional().describe("Case-insensitive substring of name or exact encoded path; not a regex."),
42222
+ offset: z23.number().int().min(0).optional(),
42223
+ limit: z23.number().int().min(1).max(100).optional(),
42224
+ placement: z23.boolean().optional().describe("Add world position, rotation, scale, mirroring and bounds per part; pages of 50.")
42007
42225
  }).strict();
42008
42226
  var PLACEMENT_PAGE_LIMIT = 50;
42009
42227
  async function listPartNames2(root, options = {}) {
@@ -42470,35 +42688,35 @@ function createKilnViewInteriorDef(context = {}) {
42470
42688
  };
42471
42689
  }
42472
42690
  var kilnViewInteriorDef = createKilnViewInteriorDef();
42473
- var attachmentEndpointInput = z21.object({
42474
- subject: z21.object({ path: z21.string().optional(), name: z21.string().optional() }).strict(),
42691
+ var attachmentEndpointInput = z23.object({
42692
+ subject: z23.object({ path: z23.string().optional(), name: z23.string().optional() }).strict(),
42475
42693
  point: cameraVec3Input.optional()
42476
42694
  }).strict();
42477
- var surfacePairInput = z21.array(z21.string().max(4096)).length(2);
42478
- var inspectInput = z21.object({
42479
- image: z21.boolean().optional().describe("False: requires listParts/measure/surfacePairs/compare; no image or camera controls. Default true."),
42695
+ var surfacePairInput = z23.array(z23.string().max(4096)).length(2);
42696
+ var inspectInput = z23.object({
42697
+ image: z23.boolean().optional().describe("False: requires listParts/measure/surfacePairs/compare; no image or camera controls. Default true."),
42480
42698
  detail: reviewDetailInput,
42481
42699
  listParts: partListInput.optional().describe("List exported-scene paths, including nested parts. Default 80, max 100 per page. Follow partListing.nextOffset with the same programRef/query. image:false avoids rendering."),
42482
- surfacePairs: z21.array(surfacePairInput).min(1).max(12).optional().describe("[from,to] pairs of exact listParts paths or unambiguous node names; check surfaceMeasurements.status and each result."),
42483
- compare: z21.object({
42484
- programRef: z21.string().regex(programRefPattern),
42485
- offset: z21.number().int().min(0).optional(),
42486
- limit: z21.number().int().min(1).max(100).optional(),
42487
- paths: z21.array(z21.string().max(4096)).min(1).max(12).optional().describe("Exact baseline node paths, scene-prefixed without primitive children. Complete subtree summaries.")
42700
+ surfacePairs: z23.array(surfacePairInput).min(1).max(12).optional().describe("[from,to] pairs of exact listParts paths or unambiguous node names; check surfaceMeasurements.status and each result."),
42701
+ compare: z23.object({
42702
+ programRef: z23.string().regex(programRefPattern),
42703
+ offset: z23.number().int().min(0).optional(),
42704
+ limit: z23.number().int().min(1).max(100).optional(),
42705
+ paths: z23.array(z23.string().max(4096)).min(1).max(12).optional().describe("Exact baseline node paths, scene-prefixed without primitive children. Complete subtree summaries.")
42488
42706
  }).strict().optional().describe("Static geometry/material/transform/bounds under current host settings. Follow nextOffset; paths adds complete subtrees."),
42489
- measure: z21.object({
42490
- mode: z21.enum(["anchors", "surface"]).optional(),
42707
+ measure: z23.object({
42708
+ mode: z23.enum(["anchors", "surface"]).optional(),
42491
42709
  from: attachmentEndpointInput,
42492
42710
  to: attachmentEndpointInput
42493
42711
  }).strict().optional().describe("Default anchors: origin/local-point distance. Surface: disjoint mesh triangles, omit points. Rest pose, asset units. Check status/bounds; no solid clearance/attachment proof."),
42494
42712
  shot: cameraShotInput.optional().describe("Exact shot; omit part/view/orbit controls."),
42495
- code: z21.string().describe("Kiln source code to execute and inspect."),
42496
- part: z21.string().optional().describe("Frame named part and descendants (case-insensitive, substring fallback). Omit for whole asset."),
42497
- view: z21.string().optional().describe("front/right/back/left/top/three-quarter (default). Orbit angles override."),
42498
- azimuthDeg: z21.number().optional().describe("Orbit degrees: 0 front, 90 right, 180 back, 270 left. Wraps."),
42499
- elevationDeg: z21.number().optional().describe("Elevation degrees: 0 eye level, positive above. Clamped -89..89."),
42500
- zoom: z21.number().optional().describe("Bounds padding 1..4; default 1.2. Larger = more context."),
42501
- isolate: z21.boolean().optional().describe("Hide surrounding geometry. Requires part; default false.")
42713
+ code: z23.string().describe("Kiln source code to execute and inspect."),
42714
+ part: z23.string().optional().describe("Frame named part and descendants (case-insensitive, substring fallback). Omit for whole asset."),
42715
+ view: z23.string().optional().describe("front/right/back/left/top/three-quarter (default). Orbit angles override."),
42716
+ azimuthDeg: z23.number().optional().describe("Orbit degrees: 0 front, 90 right, 180 back, 270 left. Wraps."),
42717
+ elevationDeg: z23.number().optional().describe("Elevation degrees: 0 eye level, positive above. Clamped -89..89."),
42718
+ zoom: z23.number().optional().describe("Bounds padding 1..4; default 1.2. Larger = more context."),
42719
+ isolate: z23.boolean().optional().describe("Hide surrounding geometry. Requires part; default false.")
42502
42720
  });
42503
42721
  var inspectBufferInput = inspectInput.omit({ code: true });
42504
42722
  var inspectAdvertisedInput = inspectInput.extend({ shot: cameraShotRecordInput });
@@ -42658,15 +42876,15 @@ function createKilnInspectDef(context = {}) {
42658
42876
  };
42659
42877
  }
42660
42878
  var kilnInspectDef = createKilnInspectDef();
42661
- var editOperationInput = z21.object({
42662
- oldString: z21.string().describe("The exact text to replace, copied verbatim from the program (including whitespace and indentation, and with no line-number prefixes). Must be unique unless replaceAll is true."),
42663
- newString: z21.string().describe("The replacement text. Use an empty string to delete."),
42664
- replaceAll: z21.boolean().optional().describe("Replace every occurrence instead of failing when oldString matches more than once.")
42879
+ var editOperationInput = z23.object({
42880
+ oldString: z23.string().describe("The exact text to replace, copied verbatim from the program (including whitespace and indentation, and with no line-number prefixes). Must be unique unless replaceAll is true."),
42881
+ newString: z23.string().describe("The replacement text. Use an empty string to delete."),
42882
+ replaceAll: z23.boolean().optional().describe("Replace every occurrence instead of failing when oldString matches more than once.")
42665
42883
  });
42666
- var editInput = z21.object({
42667
- code: z21.string().describe("The Kiln program to patch. The full current source."),
42668
- edits: z21.array(editOperationInput).min(1).max(20).describe("Edits applied in order against the program. If any one fails to match, none are applied and the reply says which. Batch related changes into a single call."),
42669
- render: z21.boolean().optional().describe("Render the patched program and return the views (default true). false = patch only."),
42884
+ var editInput = z23.object({
42885
+ code: z23.string().describe("The Kiln program to patch. The full current source."),
42886
+ edits: z23.array(editOperationInput).min(1).max(20).describe("Edits applied in order against the program. If any one fails to match, none are applied and the reply says which. Batch related changes into a single call."),
42887
+ render: z23.boolean().optional().describe("Render the patched program and return the views (default true). false = patch only."),
42670
42888
  capture: captureInput,
42671
42889
  detail: reviewDetailInput
42672
42890
  });
@@ -42892,24 +43110,27 @@ function withCaptureCache(context) {
42892
43110
  } : {}
42893
43111
  };
42894
43112
  }
42895
- var rendererInput = z21.strictObject({
42896
- action: z21.enum(["status", "reprobe"]).default("status")
43113
+ var rendererInput = z23.strictObject({
43114
+ action: z23.enum(["status", "reprobe"]).default("status")
42897
43115
  });
42898
- async function readSavedRevision(target, collection, assetId, revisionId) {
43116
+ async function readSavedRevision(target, collection, assetId, revisionId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
42899
43117
  try {
42900
43118
  return await target.read(collection, assetId, revisionId);
42901
43119
  } catch (error) {
42902
43120
  if (error.code === "ENOENT")
42903
- throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}. kiln_assets { action: 'list', collection: '${collection}' } lists the saved revisions.`);
43121
+ throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}. ${listCall} lists the saved revisions.`);
42904
43122
  throw error;
42905
43123
  }
42906
43124
  }
42907
43125
  function createKilnProgramToolRegistry(suppliedContext = {}) {
43126
+ return createProgramDefs(suppliedContext, "grouped");
43127
+ }
43128
+ function createProgramDefs(suppliedContext, presentation) {
42908
43129
  toolRequirements(suppliedContext);
42909
43130
  const store = suppliedContext.programStore ?? new MemoryProgramStore;
42910
43131
  const context = withCaptureCache(withBuildCache({ ...suppliedContext, programStore: store }));
42911
43132
  return [
42912
- createKilnDiscoveryDef({ ...suppliedContext, programStore: store }),
43133
+ createKilnDiscoveryDef({ ...suppliedContext, programStore: store }, presentation),
42913
43134
  {
42914
43135
  name: "kiln_renderer",
42915
43136
  description: "Inspect status, or reprobe after renderer setup/repair to refresh this session and reset failed starts. Never installs, starts, stops or renders. Preserves CPU/local/remote selection; environment/credential changes require a host restart. Read viewFidelity after rendering.",
@@ -42949,7 +43170,7 @@ function createKilnProgramToolRegistry(suppliedContext = {}) {
42949
43170
  ].map((def) => withProgramReferences(def, store, context.readSourceFile)),
42950
43171
  createKilnSourceDef(store),
42951
43172
  ...context.projectStore ? [createKilnProjectDef(context.projectStore, context.projectBundleReader)] : [],
42952
- ...context.materialLibrary ? [createKilnMaterialDef(context.materialLibrary)] : [],
43173
+ ...context.materialLibrary ? [createKilnMaterialDef(context.materialLibrary, presentation)] : [],
42953
43174
  ...context.reviewStore && context.assetLibrary ? [
42954
43175
  createKilnReviewDef({
42955
43176
  reviewStore: context.reviewStore,
@@ -42957,7 +43178,7 @@ function createKilnProgramToolRegistry(suppliedContext = {}) {
42957
43178
  requirements: context.requirements
42958
43179
  })
42959
43180
  ] : [],
42960
- ...createKilnAssetDefs({ ...context, programStore: store })
43181
+ ...createKilnAssetDefs({ ...context, programStore: store }, presentation)
42961
43182
  ].map((def) => withWorkspaceContext(def, context)).map((def) => ({
42962
43183
  ...def,
42963
43184
  annotations: {
@@ -42969,15 +43190,45 @@ function createKilnProgramToolRegistry(suppliedContext = {}) {
42969
43190
  }
42970
43191
  }));
42971
43192
  }
43193
+ function createKilnOperationToolRegistry(context = {}) {
43194
+ return projectOperationDefs(createProgramDefs(context, "operations"), context);
43195
+ }
42972
43196
  var assetSelector = {
42973
- collection: z21.string().regex(/^[a-z][a-z0-9_-]{0,79}$/).describe("Collection ID (list with kiln_assets action=collections): the user destination, else project.").default("project"),
42974
- assetId: z21.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
42975
- revisionId: z21.string().regex(/^[a-z][a-z0-9_-]{0,79}$/)
43197
+ collection: z23.string().regex(/^[a-z][a-z0-9_-]{0,79}$/).describe("Collection ID (list with kiln_assets action=collections): the user destination, else project.").default("project"),
43198
+ assetId: z23.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
43199
+ revisionId: z23.string().regex(/^[a-z][a-z0-9_-]{0,79}$/)
42976
43200
  };
42977
- async function newestRevision(target, collection, assetId) {
43201
+ var ASSET_CONTRACT = {
43202
+ collections: {
43203
+ fields: [],
43204
+ required: [],
43205
+ description: "List configured asset storage collections. Collection is not project membership."
43206
+ },
43207
+ catalog: {
43208
+ fields: ["assetId", "query", "offset", "limit"],
43209
+ required: [],
43210
+ description: "Search saved assets across configured collections with pagination."
43211
+ },
43212
+ list: {
43213
+ fields: ["collection", "assetId", "query", "offset", "limit"],
43214
+ required: [],
43215
+ description: "Search saved assets in one collection with pagination."
43216
+ },
43217
+ get: {
43218
+ fields: ["collection", "assetId", "revisionId"],
43219
+ required: ["assetId"],
43220
+ description: "Read a saved revision and its download descriptors. Omit revisionId for the newest revision."
43221
+ },
43222
+ restore: {
43223
+ fields: ["collection", "assetId", "revisionId"],
43224
+ required: ["assetId"],
43225
+ description: "Restore exact saved source as programRef for editing or rendering. Omit revisionId for the newest revision. Does not overwrite the saved revision. Binary-only assets have no source."
43226
+ }
43227
+ };
43228
+ async function newestRevision(target, collection, assetId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
42978
43229
  const revisions = (await target.list(collection)).filter((a) => a.assetId === assetId);
42979
43230
  if (!revisions.length)
42980
- throw new Error(`Unknown asset ${assetId} in ${collection}; kiln_assets { action: 'list', collection: '${collection}' } lists them.`);
43231
+ throw new Error(`Unknown asset ${assetId} in ${collection}; ${listCall} lists them.`);
42981
43232
  const parents = new Set(revisions.map((a) => a.parentRevision).filter(Boolean));
42982
43233
  const heads = revisions.filter((a) => !parents.has(a.revisionId));
42983
43234
  const newest = (heads.length ? heads : revisions).sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
@@ -43042,7 +43293,12 @@ async function buildProgramAssetDraft(code, context, backdrop) {
43042
43293
  }
43043
43294
  };
43044
43295
  }
43045
- function createKilnAssetDefs(context) {
43296
+ function createKilnAssetDefs(context, presentation = "grouped") {
43297
+ const listCall = (collection) => presentation === "operations" ? `kiln_assets_search({ collection: '${collection}' })` : `kiln_assets { action: 'list', collection: '${collection}' }`;
43298
+ const selector = presentation === "operations" ? {
43299
+ ...assetSelector,
43300
+ collection: assetSelector.collection.describe("Collection ID from kiln_assets_collections; default project.")
43301
+ } : assetSelector;
43046
43302
  const library = () => {
43047
43303
  if (!context.assetLibrary)
43048
43304
  throw new Error("No asset library configured. The local CLI/MCP host supplies workspace collections; embedded hosts must inject assetLibrary.");
@@ -43070,38 +43326,38 @@ function createKilnAssetDefs(context) {
43070
43326
  resources: (await Promise.resolve().then(() => (init_assets_resources(), exports_assets_resources))).assetLinks(collection, asset),
43071
43327
  downloadUrls: await context.assetDownloadUrls?.(collection, asset.assetId, asset.revisionId)
43072
43328
  });
43073
- const saveInput = z21.object({
43074
- collection: assetSelector.collection,
43075
- programRef: z21.string(),
43076
- name: z21.string().min(1).max(200),
43077
- assetId: assetSelector.assetId.optional(),
43078
- parentRevision: assetSelector.revisionId.optional(),
43079
- tags: z21.array(z21.string().max(80)).max(30).optional(),
43080
- brief: z21.string().max(8000).optional(),
43081
- description: z21.string().max(4000).optional(),
43082
- attribution: z21.object({
43083
- model: z21.string().max(200).optional(),
43084
- harness: z21.string().max(200).optional(),
43085
- author: z21.string().max(200).optional()
43329
+ const saveInput = z23.object({
43330
+ collection: selector.collection,
43331
+ programRef: z23.string(),
43332
+ name: z23.string().min(1).max(200),
43333
+ assetId: selector.assetId.optional(),
43334
+ parentRevision: selector.revisionId.optional(),
43335
+ tags: z23.array(z23.string().max(80)).max(30).optional(),
43336
+ brief: z23.string().max(8000).optional(),
43337
+ description: z23.string().max(4000).optional(),
43338
+ attribution: z23.object({
43339
+ model: z23.string().max(200).optional(),
43340
+ harness: z23.string().max(200).optional(),
43341
+ author: z23.string().max(200).optional()
43086
43342
  }).optional(),
43087
- backdrop: z21.enum(BACKDROP_IDS).optional().describe("Preview backdrop: the one the reviewed sheet used.")
43088
- });
43089
- const assetsInput = z21.object({
43090
- action: z21.enum(["collections", "catalog", "list", "get", "restore"]).default("list"),
43091
- collection: assetSelector.collection,
43092
- assetId: assetSelector.assetId.optional(),
43093
- revisionId: assetSelector.revisionId.optional().describe("get, restore: the saved revision; omitted, the newest."),
43094
- query: z21.string().max(200).optional(),
43095
- offset: z21.number().int().min(0).default(0),
43096
- limit: z21.number().int().min(1).max(50).default(20)
43097
- });
43098
- const exportInput = z21.object(assetSelector);
43343
+ backdrop: z23.enum(BACKDROP_IDS).optional().describe("Preview backdrop: the one the reviewed sheet used.")
43344
+ });
43345
+ const assetsInput = z23.object({
43346
+ action: z23.enum(["collections", "catalog", "list", "get", "restore"]).default("list"),
43347
+ collection: selector.collection,
43348
+ assetId: selector.assetId.optional(),
43349
+ revisionId: selector.revisionId.optional().describe("get, restore: the saved revision; omitted, the newest."),
43350
+ query: z23.string().max(200).optional(),
43351
+ offset: z23.number().int().min(0).default(0),
43352
+ limit: z23.number().int().min(1).max(50).default(20)
43353
+ });
43354
+ const exportInput = z23.object(selector);
43099
43355
  const profileExportInput = exportInput.extend({
43100
- profile: z21.enum(["editable", "runtime"]).default("editable").describe("editable preserves canonical source/GLB/build resources. runtime returns a standalone GLB and versioned review-metadata sidecar; no source bundle or geometry optimization.")
43356
+ profile: z23.enum(["editable", "runtime"]).default("editable").describe("editable preserves canonical source/GLB/build resources. runtime returns a standalone GLB and versioned review-metadata sidecar; no source bundle or geometry optimization.")
43101
43357
  });
43102
- const importInput = z21.object({
43103
- ...assetSelector,
43104
- sourceCollection: assetSelector.collection
43358
+ const importInput = z23.object({
43359
+ ...selector,
43360
+ sourceCollection: selector.collection
43105
43361
  });
43106
43362
  return [
43107
43363
  {
@@ -43117,7 +43373,7 @@ function createKilnAssetDefs(context) {
43117
43373
  requirements: activeRequirements.binding
43118
43374
  };
43119
43375
  if (input.assetId && input.parentRevision) {
43120
- const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision);
43376
+ const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision, listCall(input.collection));
43121
43377
  assertSavedRequirementsAuthorized(previous.manifest, activeRequirements);
43122
43378
  }
43123
43379
  const code = await context.programStore.get(input.programRef);
@@ -43132,6 +43388,7 @@ function createKilnAssetDefs(context) {
43132
43388
  name: "kiln_assets",
43133
43389
  description: "Browse saved assets: collections discovers storage; catalog searches all configured collections; list searches one. Both searches paginate. get returns a build record/downloads; restore loads exact source for kiln_source/kiln_edit. Collection is not project membership. Binary-only assets cannot restore source.",
43134
43390
  inputSchema: assetsInput,
43391
+ actionContract: ASSET_CONTRACT,
43135
43392
  run: async (raw) => {
43136
43393
  const input = assetsInput.parse(raw);
43137
43394
  const activeRequirements = toolRequirements(context);
@@ -43176,10 +43433,10 @@ function createKilnAssetDefs(context) {
43176
43433
  }))
43177
43434
  };
43178
43435
  }
43179
- if (!input.assetId)
43436
+ if (ASSET_CONTRACT[input.action].required.some((field) => !input[field]))
43180
43437
  throw new Error(`kiln_assets ${input.action} requires assetId; kiln_assets { action: 'list', collection: '${input.collection}' } lists them.`);
43181
- const revisionId = input.revisionId ?? await newestRevision(target, input.collection, input.assetId);
43182
- const record = await readSavedRevision(target, input.collection, input.assetId, revisionId);
43438
+ const revisionId = input.revisionId ?? await newestRevision(target, input.collection, input.assetId, listCall(input.collection));
43439
+ const record = await readSavedRevision(target, input.collection, input.assetId, revisionId, listCall(input.collection));
43183
43440
  if (input.action === "get")
43184
43441
  return links(input.collection, record.manifest);
43185
43442
  const saved = assertSavedRequirementsAuthorized(record.manifest, activeRequirements);
@@ -43199,9 +43456,9 @@ function createKilnAssetDefs(context) {
43199
43456
  name: "kiln_present",
43200
43457
  description: "Present one exact saved revision. Supporting MCP App clients show an interactive 3D card with GLB, editable ZIP, and source downloads. Every host receives exact artifact descriptors with resource URIs in the JSON result; verified hosts may also receive core MCP resource-link blocks. This tool does not launch a local browser in coding harnesses. Call after saving or when the user wants to see or download an asset.",
43201
43458
  inputSchema: exportInput,
43202
- outputSchema: z21.object({
43203
- ok: z21.literal(true),
43204
- collection: z21.string(),
43459
+ outputSchema: z23.object({
43460
+ ok: z23.literal(true),
43461
+ collection: z23.string(),
43205
43462
  asset: assetManifestSchema.pick({
43206
43463
  assetId: true,
43207
43464
  revisionId: true,
@@ -43212,25 +43469,25 @@ function createKilnAssetDefs(context) {
43212
43469
  editable: true,
43213
43470
  files: true
43214
43471
  }).extend({
43215
- build: z21.object({
43216
- engine: z21.string(),
43217
- rebuild: z21.enum(["engine-required", "external-dependencies-required"]),
43218
- warningCount: z21.number().int(),
43219
- warnings: z21.array(z21.string())
43472
+ build: z23.object({
43473
+ engine: z23.string(),
43474
+ rebuild: z23.enum(["engine-required", "external-dependencies-required"]),
43475
+ warningCount: z23.number().int(),
43476
+ warnings: z23.array(z23.string())
43220
43477
  }).optional()
43221
43478
  }),
43222
- resources: z21.array(z21.object({
43223
- type: z21.literal("resource_link"),
43224
- name: z21.string(),
43225
- uri: z21.string(),
43226
- mimeType: z21.string(),
43227
- size: z21.number().int().nonnegative(),
43228
- annotations: z21.object({
43229
- audience: z21.array(z21.enum(["user", "assistant"])),
43230
- priority: z21.number()
43479
+ resources: z23.array(z23.object({
43480
+ type: z23.literal("resource_link"),
43481
+ name: z23.string(),
43482
+ uri: z23.string(),
43483
+ mimeType: z23.string(),
43484
+ size: z23.number().int().nonnegative(),
43485
+ annotations: z23.object({
43486
+ audience: z23.array(z23.enum(["user", "assistant"])),
43487
+ priority: z23.number()
43231
43488
  })
43232
43489
  })),
43233
- downloadUrls: z21.record(z21.string(), z21.string()).optional()
43490
+ downloadUrls: z23.record(z23.string(), z23.string()).optional()
43234
43491
  }),
43235
43492
  ui: {
43236
43493
  resourceUri: KILN_ASSET_WIDGET_URI2,
@@ -43247,7 +43504,7 @@ function createKilnAssetDefs(context) {
43247
43504
  inputSchema: profileExportInput,
43248
43505
  run: async (raw) => {
43249
43506
  const input = profileExportInput.parse(raw);
43250
- const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId);
43507
+ const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId, listCall(input.collection));
43251
43508
  await Promise.resolve().then(() => init_draw_diagnostics());
43252
43509
  if (input.profile === "runtime") {
43253
43510
  const output = await (await Promise.resolve().then(() => (init_asset_export(), exports_asset_export))).exportAssetGlb(record, {
@@ -43281,7 +43538,7 @@ function createKilnAssetDefs(context) {
43281
43538
  run: async (raw) => {
43282
43539
  const input = importInput.parse(raw);
43283
43540
  const target = library();
43284
- const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId);
43541
+ const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId, listCall(input.sourceCollection));
43285
43542
  await target.import(input.collection, [record]);
43286
43543
  return links(input.collection, record.manifest);
43287
43544
  }
@@ -43405,7 +43662,7 @@ function createKilnToolHost(context = {}, options = {}) {
43405
43662
  };
43406
43663
  }
43407
43664
  };
43408
- const defs = kilnMcpToolDefs(requestContext);
43665
+ const defs = options.toolPresentation === "operations" ? createKilnOperationToolRegistry(requestContext) : kilnMcpToolDefs(requestContext);
43409
43666
  const byName = new Map(defs.map((def) => [def.name, def]));
43410
43667
  return {
43411
43668
  defs,
@@ -43472,6 +43729,7 @@ function resourceText(name, bytes) {
43472
43729
  function createKilnMcpServer(context = {}, options = {}) {
43473
43730
  const host = createKilnToolHost(context, options);
43474
43731
  return createKilnServer({
43732
+ ...options.toolPresentation === "operations" ? { instructions: MCP_OPERATION_INSTRUCTIONS } : {},
43475
43733
  manifest: buildMcpManifest(host.defs, context),
43476
43734
  host: async () => host
43477
43735
  });