omnius 1.0.162 → 1.0.163

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.
package/dist/index.js CHANGED
@@ -20504,7 +20504,6 @@ ${text}`,
20504
20504
  import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync17, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "node:fs";
20505
20505
  import { join as join26 } from "node:path";
20506
20506
  import { homedir as homedir7 } from "node:os";
20507
- import { spawn as spawn5 } from "node:child_process";
20508
20507
  function globalToolsDir() {
20509
20508
  return join26(homedir7(), ".omnius", "tools");
20510
20509
  }
@@ -20529,9 +20528,9 @@ function loadCustomTools(repoRoot) {
20529
20528
  }
20530
20529
  return Array.from(definitions.values());
20531
20530
  }
20532
- function buildCustomTools(repoRoot) {
20531
+ function buildCustomTools(repoRoot, options2 = {}) {
20533
20532
  const definitions = loadCustomTools(repoRoot);
20534
- return definitions.map((def) => new CustomTool(def, repoRoot));
20533
+ return definitions.map((def) => new CustomTool(def, repoRoot, [], { shellRunner: options2.shellRunner }));
20535
20534
  }
20536
20535
  function saveCustomToolDefinition(definition, scope, repoRoot) {
20537
20536
  const dir = scope === "project" && repoRoot ? projectToolsDir(repoRoot) : globalToolsDir();
@@ -20542,8 +20541,9 @@ function saveCustomToolDefinition(definition, scope, repoRoot) {
20542
20541
  const withDocsPath = { ...normalized, docsPath };
20543
20542
  const withDocs = withDocsPath.docs ? withDocsPath : { ...withDocsPath, docs: renderCustomToolDocs(withDocsPath) };
20544
20543
  const withProvenance = ensureCustomToolProvenance(withDocs);
20545
- writeFileSync10(filePath, JSON.stringify(withProvenance, null, 2), "utf-8");
20546
- writeCustomToolDocsFile(withProvenance, scope, repoRoot);
20544
+ const finalDefinition = { ...withProvenance, docs: renderCustomToolDocs(withProvenance) };
20545
+ writeFileSync10(filePath, JSON.stringify(finalDefinition, null, 2), "utf-8");
20546
+ writeCustomToolDocsFile(finalDefinition, scope, repoRoot);
20547
20547
  if (scope === "project" && repoRoot)
20548
20548
  writeCustomToolRegistry(repoRoot);
20549
20549
  return filePath;
@@ -20578,7 +20578,8 @@ function listCustomToolFiles(repoRoot) {
20578
20578
  analytics: analytics[def.name],
20579
20579
  qualityGate: def.qualityGate,
20580
20580
  docsPath: def.docsPath,
20581
- examples: def.examples
20581
+ examples: def.examples,
20582
+ shareability: evaluateCustomToolShareability(def)
20582
20583
  });
20583
20584
  seen.add(def.name);
20584
20585
  }
@@ -20594,7 +20595,8 @@ function listCustomToolFiles(repoRoot) {
20594
20595
  analytics: analytics[def.name],
20595
20596
  qualityGate: def.qualityGate,
20596
20597
  docsPath: def.docsPath,
20597
- examples: def.examples
20598
+ examples: def.examples,
20599
+ shareability: evaluateCustomToolShareability(def)
20598
20600
  });
20599
20601
  }
20600
20602
  }
@@ -20621,6 +20623,9 @@ function validateCustomToolDefinition(definition) {
20621
20623
  errors.push(`steps[${index}].description is required`);
20622
20624
  });
20623
20625
  }
20626
+ errors.push(...validateRawInterpolation(definition.steps ?? [], definition.parameters, "steps"));
20627
+ errors.push(...validateRawInterpolation(definition.beforeEach ?? [], definition.parameters, "beforeEach"));
20628
+ errors.push(...validateRawInterpolation(definition.afterAll ?? [], definition.parameters, "afterAll"));
20624
20629
  return errors;
20625
20630
  }
20626
20631
  function renderCustomToolDocs(definition) {
@@ -20673,12 +20678,16 @@ function renderCustomToolDocs(definition) {
20673
20678
  lines.push("", "## Quality Gate", `Process: ${def.qualityGate.process}`);
20674
20679
  if (last2) {
20675
20680
  lines.push(`Last test: ${last2.status}${last2.testedAt ? ` at ${last2.testedAt}` : ""}`);
20681
+ if (last2.schemaValidation)
20682
+ lines.push(`Schema validation: ${last2.schemaValidation.status}${last2.schemaValidation.error ? ` - ${last2.schemaValidation.error}` : ""}`);
20676
20683
  if (last2.args)
20677
20684
  lines.push(`Test args: \`${JSON.stringify(last2.args)}\``);
20678
20685
  if (last2.error)
20679
20686
  lines.push(`Last error: ${last2.error}`);
20680
20687
  }
20681
20688
  }
20689
+ const shareability = evaluateCustomToolShareability(def);
20690
+ lines.push("", "## Nexus Sharing Gate", `Shareable: ${shareability.shareable ? "true" : "false"} - ${shareability.reason}`);
20682
20691
  lines.push("", "## Maintenance", `Inspect: \`manage_tools(action="inspect", tool_name="${def.name}")\``, `Patch: \`manage_tools(action="patch", tool_name="${def.name}", patch={...}, test_args={...})\``, `Test: \`manage_tools(action="test", tool_name="${def.name}", test_args={...})\``);
20683
20692
  return `${lines.join("\n")}
20684
20693
  `;
@@ -20702,7 +20711,10 @@ function writeCustomToolRegistry(repoRoot) {
20702
20711
  } : void 0,
20703
20712
  qualityGate: tool.qualityGate,
20704
20713
  docsPath: tool.docsPath,
20705
- examples: tool.examples
20714
+ examples: tool.examples,
20715
+ shareability: tool.shareability,
20716
+ shareable: tool.shareability?.shareable ?? false,
20717
+ shareableReason: tool.shareability?.reason ?? "not evaluated"
20706
20718
  }));
20707
20719
  writeFileSync10(registryPath, JSON.stringify({
20708
20720
  schema: "omnius.custom-tool-registry.v1",
@@ -20738,7 +20750,7 @@ function writeCustomToolContextIndex(repoRoot, tools) {
20738
20750
  for (const tool of tools) {
20739
20751
  const last2 = tool.qualityGate?.lastTest;
20740
20752
  const example = tool.examples?.[0]?.args;
20741
- lines.push(`## ${tool.name}`, "", `${tool.description}`, "", `- Scope/version: ${tool.scope} v${tool.version}`, `- Steps: ${tool.stepsCount}`, `- Last test: ${last2?.status ?? "untested"}`);
20753
+ lines.push(`## ${tool.name}`, "", `${tool.description}`, "", `- Scope/version: ${tool.scope} v${tool.version}`, `- Steps: ${tool.stepsCount}`, `- Last test: ${last2?.status ?? "untested"}`, `- Shareable: ${tool.shareability?.shareable ? "true" : "false"}${tool.shareability?.reason ? ` - ${tool.shareability.reason}` : ""}`);
20742
20754
  if (tool.docsPath)
20743
20755
  lines.push(`- Docs: ${tool.docsPath}`);
20744
20756
  if (example)
@@ -20849,6 +20861,131 @@ function valueMatchesJsonType(value2, type) {
20849
20861
  return value2 === null;
20850
20862
  return true;
20851
20863
  }
20864
+ function stringifyParameterValue(value2) {
20865
+ if (typeof value2 === "string")
20866
+ return value2;
20867
+ if (typeof value2 === "number" || typeof value2 === "boolean" || value2 === null)
20868
+ return String(value2);
20869
+ return JSON.stringify(value2);
20870
+ }
20871
+ function escapeForExistingShellQuote(value2, quote) {
20872
+ if (quote === "'")
20873
+ return value2.replace(/'/g, `'\\''`);
20874
+ return value2.replace(/[`$\\!"]/g, "\\$&");
20875
+ }
20876
+ function parameterAllowsUnsafeRaw(schema, name10) {
20877
+ const props = schema["properties"];
20878
+ if (!props || typeof props !== "object" || Array.isArray(props))
20879
+ return false;
20880
+ const prop = props[name10];
20881
+ return prop?.["x-omnius-unsafeRaw"] === true;
20882
+ }
20883
+ function validateRawInterpolation(steps, schema, label) {
20884
+ const errors = [];
20885
+ for (let i2 = 0; i2 < steps.length; i2++) {
20886
+ const command = steps[i2]?.command;
20887
+ if (!command)
20888
+ continue;
20889
+ const matches = command.matchAll(/\{\{raw:([A-Za-z_][A-Za-z0-9_]*)\}\}/g);
20890
+ for (const match of matches) {
20891
+ const param = match[1];
20892
+ if (!parameterAllowsUnsafeRaw(schema, param)) {
20893
+ errors.push(`${label}[${i2}] uses {{raw:${param}}} but parameter ${param} does not declare x-omnius-unsafeRaw: true`);
20894
+ }
20895
+ }
20896
+ }
20897
+ return errors;
20898
+ }
20899
+ function validateOutputAgainstSchema(output, schema) {
20900
+ if (!schema)
20901
+ return { status: "not_applicable" };
20902
+ let value2;
20903
+ try {
20904
+ value2 = JSON.parse(output.trim());
20905
+ } catch {
20906
+ return {
20907
+ status: "failed",
20908
+ error: "output_schema_validation_failed: expected JSON output"
20909
+ };
20910
+ }
20911
+ const errors = validateJsonValueAgainstSchema(value2, schema, "$");
20912
+ if (errors.length > 0) {
20913
+ return {
20914
+ status: "failed",
20915
+ error: `output_schema_validation_failed: ${errors.join("; ")}`
20916
+ };
20917
+ }
20918
+ return { status: "passed" };
20919
+ }
20920
+ function validateJsonValueAgainstSchema(value2, schema, path12) {
20921
+ const errors = [];
20922
+ const expected = schema["type"];
20923
+ const allowed = Array.isArray(expected) ? expected.map(String) : expected ? [String(expected)] : [];
20924
+ if (allowed.length > 0 && !allowed.some((type) => valueMatchesJsonType(value2, type))) {
20925
+ errors.push(`${path12} expected ${allowed.join("|")}, got ${Array.isArray(value2) ? "array" : typeof value2}`);
20926
+ return errors;
20927
+ }
20928
+ const enumValues = schema["enum"];
20929
+ if (Array.isArray(enumValues) && !enumValues.includes(value2)) {
20930
+ errors.push(`${path12} must be one of ${enumValues.map(String).join(", ")}`);
20931
+ }
20932
+ const required = Array.isArray(schema["required"]) ? schema["required"].map(String) : [];
20933
+ const properties = schema["properties"] && typeof schema["properties"] === "object" && !Array.isArray(schema["properties"]) ? schema["properties"] : {};
20934
+ if (required.length > 0 || Object.keys(properties).length > 0) {
20935
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
20936
+ errors.push(`${path12} expected object for declared properties`);
20937
+ return errors;
20938
+ }
20939
+ const obj = value2;
20940
+ for (const key of required) {
20941
+ if (obj[key] === void 0)
20942
+ errors.push(`${path12}.${key} is required`);
20943
+ }
20944
+ for (const [key, propSchema] of Object.entries(properties)) {
20945
+ if (obj[key] !== void 0) {
20946
+ errors.push(...validateJsonValueAgainstSchema(obj[key], propSchema, `${path12}.${key}`));
20947
+ }
20948
+ }
20949
+ }
20950
+ if (Array.isArray(value2)) {
20951
+ const itemSchema = schema["items"];
20952
+ if (itemSchema && typeof itemSchema === "object" && !Array.isArray(itemSchema)) {
20953
+ value2.forEach((item, index) => {
20954
+ errors.push(...validateJsonValueAgainstSchema(item, itemSchema, `${path12}[${index}]`));
20955
+ });
20956
+ }
20957
+ }
20958
+ return errors;
20959
+ }
20960
+ function evaluateCustomToolShareability(definition) {
20961
+ const def = normalizeCustomToolDefinition(definition);
20962
+ if (def.qualityGate?.lastTest?.status !== "passed") {
20963
+ return { shareable: false, reason: "quality gate has not passed" };
20964
+ }
20965
+ if (!def.docsPath)
20966
+ return { shareable: false, reason: "missing generated docs path" };
20967
+ if (!def.examples || def.examples.length === 0)
20968
+ return { shareable: false, reason: "missing examples" };
20969
+ if (!def.provenance)
20970
+ return { shareable: false, reason: "missing provenance" };
20971
+ const deps = def.dependencies;
20972
+ if (!deps || !(deps.tools?.length || deps.commands?.length || deps.files?.length)) {
20973
+ return { shareable: false, reason: "missing dependency declaration" };
20974
+ }
20975
+ const props = def.parameters["properties"];
20976
+ if (props && typeof props === "object" && !Array.isArray(props)) {
20977
+ for (const [name10, prop] of Object.entries(props)) {
20978
+ if (prop?.["x-omnius-unsafeRaw"] === true) {
20979
+ return { shareable: false, reason: `unsafe raw parameter is not shareable: ${name10}` };
20980
+ }
20981
+ }
20982
+ }
20983
+ const allSteps = [...def.steps ?? [], ...def.beforeEach ?? [], ...def.afterAll ?? []];
20984
+ if (allSteps.some((step) => /\{\{raw:[A-Za-z_][A-Za-z0-9_]*\}\}/.test(step.command ?? ""))) {
20985
+ return { shareable: false, reason: "raw shell interpolation is not shareable" };
20986
+ }
20987
+ return { shareable: true, reason: "passed quality gate with docs, examples, dependencies, and provenance" };
20988
+ }
20852
20989
  function readProjectAnalytics(repoRoot) {
20853
20990
  try {
20854
20991
  const analyticsPath = join26(projectToolsDir(repoRoot), ".analytics.json");
@@ -20889,6 +21026,7 @@ var BUILTIN_TOOL_NAMES, CustomTool;
20889
21026
  var init_custom_tool = __esm({
20890
21027
  "packages/execution/dist/tools/custom-tool.js"() {
20891
21028
  "use strict";
21029
+ init_shell();
20892
21030
  init_provenance();
20893
21031
  BUILTIN_TOOL_NAMES = /* @__PURE__ */ new Set([
20894
21032
  "file_read",
@@ -20930,11 +21068,13 @@ var init_custom_tool = __esm({
20930
21068
  name;
20931
21069
  description;
20932
21070
  parameters;
21071
+ customToolMetadata;
20933
21072
  steps;
20934
21073
  workingDir;
20935
21074
  definition;
20936
21075
  executionStack;
20937
21076
  executionOptions;
21077
+ shellRunner;
20938
21078
  constructor(definition, workingDir, executionStack = [], executionOptions = {}) {
20939
21079
  this.definition = normalizeCustomToolDefinition(definition);
20940
21080
  this.name = this.definition.name;
@@ -20944,20 +21084,37 @@ var init_custom_tool = __esm({
20944
21084
  this.workingDir = workingDir;
20945
21085
  this.executionStack = executionStack;
20946
21086
  this.executionOptions = executionOptions;
21087
+ this.shellRunner = executionOptions.shellRunner ?? new ShellTool(workingDir);
21088
+ const analytics = readProjectAnalytics(workingDir)[this.definition.name] ?? this.definition.analytics;
21089
+ this.customToolMetadata = {
21090
+ isCustomTool: true,
21091
+ version: this.definition.version,
21092
+ docsPath: this.definition.docsPath,
21093
+ qualityGate: this.definition.qualityGate,
21094
+ dependencies: this.definition.dependencies,
21095
+ examples: this.definition.examples,
21096
+ analytics,
21097
+ shareability: evaluateCustomToolShareability(this.definition)
21098
+ };
20947
21099
  }
20948
21100
  async execute(args) {
20949
21101
  const start2 = performance.now();
20950
21102
  const outputs = [];
20951
21103
  let allSuccess = true;
21104
+ let lastRawOutput = "";
21105
+ let failedStep;
20952
21106
  const validation = validateArgsAgainstSchema(args, this.parameters);
20953
21107
  if (!validation.valid) {
20954
21108
  const durationMs2 = performance.now() - start2;
20955
21109
  this.recordAnalytics(false, durationMs2);
21110
+ const error = `Invalid arguments for ${this.name}:
21111
+ ${validation.errors.map((e2) => `- ${e2}`).join("\n")}`;
21112
+ const repair2 = this.buildRepairEnvelope(args, { label: "argument validation", error });
20956
21113
  return {
20957
21114
  success: false,
20958
- output: "",
20959
- error: `Invalid arguments for ${this.name}:
20960
- ${validation.errors.map((e2) => `- ${e2}`).join("\n")}`,
21115
+ output: repair2,
21116
+ llmContent: repair2,
21117
+ error,
20961
21118
  durationMs: durationMs2
20962
21119
  };
20963
21120
  }
@@ -20965,11 +21122,14 @@ ${validation.errors.map((e2) => `- ${e2}`).join("\n")}`,
20965
21122
  if (dependencyErrors.length > 0) {
20966
21123
  const durationMs2 = performance.now() - start2;
20967
21124
  this.recordAnalytics(false, durationMs2);
21125
+ const error = `Custom tool dependency check failed:
21126
+ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`;
21127
+ const repair2 = this.buildRepairEnvelope(args, { label: "dependency validation", error });
20968
21128
  return {
20969
21129
  success: false,
20970
- output: "",
20971
- error: `Custom tool dependency check failed:
20972
- ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21130
+ output: repair2,
21131
+ llmContent: repair2,
21132
+ error,
20973
21133
  durationMs: durationMs2
20974
21134
  };
20975
21135
  }
@@ -20995,10 +21155,12 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
20995
21155
  try {
20996
21156
  const result = step.tool ? await this.runNestedTool(step, args) : await this.runShellStep(step, args);
20997
21157
  outputs.push(result.output);
21158
+ lastRawOutput = result.rawOutput ?? result.output;
20998
21159
  if (!result.success) {
20999
21160
  allSuccess = false;
21000
21161
  if (result.error)
21001
21162
  outputs.push(`Error: ${result.error}`);
21163
+ failedStep = { label, step, output: result.output, error: result.error };
21002
21164
  if (!step.continueOnError) {
21003
21165
  outputs.push(`${label} failed. Stopping.`);
21004
21166
  break;
@@ -21007,32 +21169,67 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21007
21169
  }
21008
21170
  } catch (err) {
21009
21171
  allSuccess = false;
21010
- outputs.push(`${label} error: ${err instanceof Error ? err.message : String(err)}`);
21172
+ const error = err instanceof Error ? err.message : String(err);
21173
+ outputs.push(`${label} error: ${error}`);
21174
+ failedStep = { label, step, output: "", error };
21011
21175
  if (!step.continueOnError)
21012
21176
  break;
21013
21177
  }
21014
21178
  }
21179
+ const schemaValidation = validateOutputAgainstSchema(lastRawOutput || outputs.join("\n"), this.definition.outputSchema);
21180
+ if (schemaValidation.status === "passed") {
21181
+ outputs.push("Output schema validation: passed");
21182
+ }
21183
+ if (schemaValidation.status === "failed") {
21184
+ allSuccess = false;
21185
+ outputs.push(`Output schema validation failed: ${schemaValidation.error}`);
21186
+ failedStep ??= {
21187
+ label: "output schema validation",
21188
+ step: { description: "Validate declared output schema" },
21189
+ output: lastRawOutput || outputs.join("\n"),
21190
+ error: schemaValidation.error
21191
+ };
21192
+ }
21015
21193
  const durationMs = performance.now() - start2;
21016
21194
  this.recordAnalytics(allSuccess, durationMs);
21195
+ const output = outputs.join("\n");
21196
+ const repair = !allSuccess ? this.buildRepairEnvelope(args, failedStep) : void 0;
21017
21197
  return {
21018
21198
  success: allSuccess,
21019
- output: outputs.join("\n"),
21199
+ output: repair ? `${output}
21200
+
21201
+ ${repair}` : output,
21202
+ llmContent: repair ? `${output}
21203
+
21204
+ ${repair}` : output,
21020
21205
  error: allSuccess ? void 0 : "One or more steps failed",
21021
21206
  durationMs
21022
21207
  };
21023
21208
  }
21024
- /** Replace {{param}} placeholders with actual argument values */
21209
+ /** Replace {{param}} placeholders with shell-safe argument values. */
21025
21210
  interpolate(template, args) {
21026
- return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
21211
+ return template.replace(/\{\{(?:(shell|json|raw):)?([A-Za-z_][A-Za-z0-9_]*)\}\}/g, (match, mode, key, offset, source) => {
21027
21212
  const val = args[key];
21028
21213
  if (val === void 0 || val === null)
21029
21214
  return "";
21030
- return String(val).replace(/[`$\\!"]/g, "\\$&");
21215
+ const normalizedMode = mode ?? "shell";
21216
+ const quote = source[offset - 1];
21217
+ const closedBySameQuote = (quote === "'" || quote === '"') && source[offset + match.length] === quote;
21218
+ if (normalizedMode === "raw") {
21219
+ if (!parameterAllowsUnsafeRaw(this.parameters, key)) {
21220
+ throw new Error(`Unsafe raw interpolation rejected for parameter "${key}". Add x-omnius-unsafeRaw: true to the parameter schema only when raw shell insertion is intentional.`);
21221
+ }
21222
+ return stringifyParameterValue(val);
21223
+ }
21224
+ const text = normalizedMode === "json" ? JSON.stringify(val) : stringifyParameterValue(val);
21225
+ if (closedBySameQuote)
21226
+ return escapeForExistingShellQuote(text, quote);
21227
+ return this.shellQuote(text);
21031
21228
  });
21032
21229
  }
21033
21230
  interpolateValue(value2, args) {
21034
21231
  if (typeof value2 === "string")
21035
- return this.interpolate(value2, args);
21232
+ return this.interpolateNestedValue(value2, args);
21036
21233
  if (Array.isArray(value2))
21037
21234
  return value2.map((item) => this.interpolateValue(item, args));
21038
21235
  if (value2 && typeof value2 === "object") {
@@ -21043,6 +21240,16 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21043
21240
  }
21044
21241
  return value2;
21045
21242
  }
21243
+ interpolateNestedValue(template, args) {
21244
+ return template.replace(/\{\{(?:(shell|json|raw):)?([A-Za-z_][A-Za-z0-9_]*)\}\}/g, (_, mode, key) => {
21245
+ const val = args[key];
21246
+ if (val === void 0 || val === null)
21247
+ return "";
21248
+ if (mode === "json")
21249
+ return JSON.stringify(val);
21250
+ return stringifyParameterValue(val);
21251
+ });
21252
+ }
21046
21253
  shouldRunStep(step, args) {
21047
21254
  if (!step.when)
21048
21255
  return true;
@@ -21057,10 +21264,14 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21057
21264
  const command = this.interpolate(step.command ?? "", args);
21058
21265
  if (!command)
21059
21266
  return { success: false, output: "", error: "Shell step is missing command" };
21060
- const result = await this.runCommand(command);
21267
+ const result = await this.shellRunner.execute({ command, timeout: 6e4 });
21268
+ const modelFacing = result.llmContent ?? result.output;
21061
21269
  return {
21062
- ...result,
21063
- output: [`$ ${command}`, result.output].filter(Boolean).join("\n")
21270
+ success: result.success,
21271
+ output: [`$ ${command}`, modelFacing].filter(Boolean).join("\n"),
21272
+ rawOutput: result.output,
21273
+ llmContent: modelFacing,
21274
+ error: result.error
21064
21275
  };
21065
21276
  }
21066
21277
  async runNestedTool(step, args) {
@@ -21081,6 +21292,8 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21081
21292
  return {
21082
21293
  success: result.success,
21083
21294
  output: [`custom_tool ${toolName}(${JSON.stringify(nestedArgs)})`, result.output].join("\n"),
21295
+ rawOutput: result.llmContent ?? result.output,
21296
+ llmContent: result.llmContent,
21084
21297
  error: result.error
21085
21298
  };
21086
21299
  }
@@ -21100,7 +21313,7 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21100
21313
  errors.push(`file dependency not found: ${file}`);
21101
21314
  }
21102
21315
  for (const command of deps.commands ?? []) {
21103
- const check = await this.runCommand(`command -v ${this.shellQuote(command)}`);
21316
+ const check = await this.shellRunner.execute({ command: `command -v ${this.shellQuote(command)}`, timeout: 3e4 });
21104
21317
  if (!check.success)
21105
21318
  errors.push(`command dependency not found on PATH: ${command}`);
21106
21319
  }
@@ -21109,6 +21322,25 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21109
21322
  shellQuote(value2) {
21110
21323
  return `'${value2.replace(/'/g, `'\\''`)}'`;
21111
21324
  }
21325
+ buildRepairEnvelope(args, failure) {
21326
+ return [
21327
+ "[CUSTOM_TOOL_REPAIR_ENVELOPE]",
21328
+ `tool: ${this.name}`,
21329
+ `version: ${this.definition.version}`,
21330
+ this.definition.docsPath ? `docs_path: ${this.definition.docsPath}` : "docs_path: [missing]",
21331
+ `args: ${JSON.stringify(args)}`,
21332
+ failure?.label ? `failed_step: ${failure.label}` : "failed_step: unknown",
21333
+ failure?.step?.description ? `failed_step_description: ${failure.step.description}` : "",
21334
+ failure?.error ? `error: ${failure.error}` : "",
21335
+ failure?.output ? `output:
21336
+ ${String(failure.output).slice(0, 4e3)}` : "",
21337
+ `quality_gate: ${JSON.stringify(this.definition.qualityGate ?? { lastTest: { status: "untested" } })}`,
21338
+ "repair_action: inspect the docs and patch the tool definition, then run its smoke test before relying on it again.",
21339
+ `suggested_patch_call: manage_tools(action="patch", tool_name="${this.name}", patch={...}, test_args=${JSON.stringify(args)})`,
21340
+ `suggested_test_call: manage_tools(action="test", tool_name="${this.name}", test_args=${JSON.stringify(args)})`,
21341
+ "[/CUSTOM_TOOL_REPAIR_ENVELOPE]"
21342
+ ].filter(Boolean).join("\n");
21343
+ }
21112
21344
  recordAnalytics(success, durationMs) {
21113
21345
  if (this.executionOptions.recordAnalytics === false)
21114
21346
  return;
@@ -21138,51 +21370,6 @@ ${dependencyErrors.map((e2) => `- ${e2}`).join("\n")}`,
21138
21370
  } catch {
21139
21371
  }
21140
21372
  }
21141
- /** Execute a single shell command and return output */
21142
- runCommand(command) {
21143
- return new Promise((resolve57) => {
21144
- const child = spawn5("bash", ["-c", command], {
21145
- cwd: this.workingDir,
21146
- env: { ...process.env, CI: "true", NO_COLOR: "1" },
21147
- stdio: ["pipe", "pipe", "pipe"]
21148
- });
21149
- let stdout = "";
21150
- let stderr = "";
21151
- const maxBuf = 512 * 1024;
21152
- child.stdout.on("data", (data) => {
21153
- stdout += data.toString();
21154
- if (stdout.length > maxBuf)
21155
- stdout = stdout.slice(0, maxBuf);
21156
- });
21157
- child.stderr.on("data", (data) => {
21158
- stderr += data.toString();
21159
- if (stderr.length > maxBuf)
21160
- stderr = stderr.slice(0, maxBuf);
21161
- });
21162
- child.stdin.end();
21163
- const timer = setTimeout(() => {
21164
- try {
21165
- child.kill("SIGTERM");
21166
- } catch {
21167
- }
21168
- resolve57({ success: false, output: stdout, error: "Command timed out after 60s" });
21169
- }, 6e4);
21170
- child.on("close", (code8) => {
21171
- clearTimeout(timer);
21172
- resolve57({
21173
- success: code8 === 0,
21174
- output: stdout + (stderr && code8 === 0 ? `
21175
- STDERR:
21176
- ${stderr}` : ""),
21177
- error: code8 !== 0 ? stderr || `Exit code ${code8}` : void 0
21178
- });
21179
- });
21180
- child.on("error", (err) => {
21181
- clearTimeout(timer);
21182
- resolve57({ success: false, output: stdout, error: err.message });
21183
- });
21184
- });
21185
- }
21186
21373
  };
21187
21374
  }
21188
21375
  });
@@ -21195,17 +21382,17 @@ function templateSteps(template) {
21195
21382
  switch (template) {
21196
21383
  case "build_test":
21197
21384
  return [
21198
- { command: "{{build_command}}", description: "Run build command" },
21199
- { command: "{{test_command}}", description: "Run test command" }
21385
+ { command: "{{raw:build_command}}", description: "Run build command" },
21386
+ { command: "{{raw:test_command}}", description: "Run test command" }
21200
21387
  ];
21201
21388
  case "test_only":
21202
21389
  return [
21203
- { command: "{{test_command}}", description: "Run test command" }
21390
+ { command: "{{raw:test_command}}", description: "Run test command" }
21204
21391
  ];
21205
21392
  case "diagnostic":
21206
21393
  return [
21207
21394
  { command: "git status --short", description: "Inspect working tree" },
21208
- { command: "{{diagnostic_command}}", description: "Run diagnostic command" }
21395
+ { command: "{{raw:diagnostic_command}}", description: "Run diagnostic command" }
21209
21396
  ];
21210
21397
  case "git_preflight":
21211
21398
  return [
@@ -21219,14 +21406,14 @@ function templateSteps(template) {
21219
21406
  function mergeTemplateParameters(template, provided) {
21220
21407
  const hasProperties = provided["properties"] && typeof provided["properties"] === "object" && Object.keys(provided["properties"]).length > 0;
21221
21408
  if (hasProperties)
21222
- return provided;
21409
+ return applyTemplateRawOptIns(template, provided);
21223
21410
  switch (template) {
21224
21411
  case "build_test":
21225
21412
  return {
21226
21413
  type: "object",
21227
21414
  properties: {
21228
- build_command: { type: "string", description: "Build command to run" },
21229
- test_command: { type: "string", description: "Test command to run" }
21415
+ build_command: { type: "string", description: "Build command to run", "x-omnius-unsafeRaw": true },
21416
+ test_command: { type: "string", description: "Test command to run", "x-omnius-unsafeRaw": true }
21230
21417
  },
21231
21418
  required: ["build_command", "test_command"]
21232
21419
  };
@@ -21234,7 +21421,7 @@ function mergeTemplateParameters(template, provided) {
21234
21421
  return {
21235
21422
  type: "object",
21236
21423
  properties: {
21237
- test_command: { type: "string", description: "Test command to run" }
21424
+ test_command: { type: "string", description: "Test command to run", "x-omnius-unsafeRaw": true }
21238
21425
  },
21239
21426
  required: ["test_command"]
21240
21427
  };
@@ -21242,7 +21429,7 @@ function mergeTemplateParameters(template, provided) {
21242
21429
  return {
21243
21430
  type: "object",
21244
21431
  properties: {
21245
- diagnostic_command: { type: "string", description: "Diagnostic command to run" }
21432
+ diagnostic_command: { type: "string", description: "Diagnostic command to run", "x-omnius-unsafeRaw": true }
21246
21433
  },
21247
21434
  required: ["diagnostic_command"]
21248
21435
  };
@@ -21250,6 +21437,21 @@ function mergeTemplateParameters(template, provided) {
21250
21437
  return provided;
21251
21438
  }
21252
21439
  }
21440
+ function applyTemplateRawOptIns(template, provided) {
21441
+ const rawParams = template === "build_test" ? ["build_command", "test_command"] : template === "test_only" ? ["test_command"] : template === "diagnostic" ? ["diagnostic_command"] : [];
21442
+ if (rawParams.length === 0)
21443
+ return provided;
21444
+ const properties = provided["properties"];
21445
+ if (!properties || typeof properties !== "object" || Array.isArray(properties))
21446
+ return provided;
21447
+ const nextProperties = { ...properties };
21448
+ for (const param of rawParams) {
21449
+ if (nextProperties[param]) {
21450
+ nextProperties[param] = { ...nextProperties[param], "x-omnius-unsafeRaw": true };
21451
+ }
21452
+ }
21453
+ return { ...provided, properties: nextProperties };
21454
+ }
21253
21455
  function parseStep(raw, index) {
21254
21456
  const command = String(raw["command"] ?? "").trim();
21255
21457
  const tool = String(raw["tool"] ?? "").trim();
@@ -21334,7 +21536,22 @@ async function runToolSmokeTest(definition, repoRoot, args) {
21334
21536
  testedAt: (/* @__PURE__ */ new Date()).toISOString(),
21335
21537
  durationMs: result.durationMs,
21336
21538
  outputPreview: truncateText(result.output, 2e3),
21337
- error: result.error
21539
+ error: result.error,
21540
+ schemaValidation: schemaValidationForTest(definition, result)
21541
+ };
21542
+ }
21543
+ function schemaValidationForTest(definition, result) {
21544
+ if (!definition.outputSchema)
21545
+ return { status: "not_applicable" };
21546
+ const combined = `${result.output}
21547
+ ${result.llmContent ?? ""}
21548
+ ${result.error ?? ""}`;
21549
+ if (combined.includes("Output schema validation: passed"))
21550
+ return { status: "passed" };
21551
+ const failed = combined.match(/Output schema validation failed:\s*([^\n]+)/);
21552
+ return {
21553
+ status: "failed",
21554
+ error: failed?.[1] ?? result.error ?? "output_schema_validation_failed"
21338
21555
  };
21339
21556
  }
21340
21557
  function truncateText(value2, max) {
@@ -21456,7 +21673,7 @@ var init_tool_creator = __esm({
21456
21673
  ]);
21457
21674
  CreateToolTool = class {
21458
21675
  name = "create_tool";
21459
- description = "Create a reusable custom tool from a multi-step workflow. Use this when you notice you're performing the same sequence of operations repeatedly (3+ times). The tool will be saved and available in future sessions. Parameters in step commands use {{param}} interpolation.";
21676
+ description = "Create a reusable custom tool from a multi-step workflow. Use this when you notice you're performing the same sequence of operations repeatedly (3+ times). The tool will be saved and available in future sessions. Parameters in shell step commands use shell-safe {{param}} interpolation; use {{json:param}} for JSON and {{raw:param}} only when the parameter schema explicitly opts into x-omnius-unsafeRaw.";
21460
21677
  parameters = {
21461
21678
  type: "object",
21462
21679
  properties: {
@@ -21474,7 +21691,7 @@ var init_tool_creator = __esm({
21474
21691
  },
21475
21692
  steps: {
21476
21693
  type: "array",
21477
- description: "Ordered list of steps. Each step has either command (shell command with {{param}} placeholders) or tool (nested custom tool name) with args, plus description, continueOnError, and optional when condition.",
21694
+ description: "Ordered list of steps. Each step has either command (shell command with {{param}} placeholders) or tool (nested custom tool name) with args, plus description, continueOnError, and optional when condition. {{param}} and {{shell:param}} are shell-escaped, {{json:param}} JSON-encodes, and {{raw:param}} requires x-omnius-unsafeRaw on that parameter.",
21478
21695
  items: {
21479
21696
  type: "object",
21480
21697
  properties: {
@@ -21819,6 +22036,8 @@ ${errors.map((err) => `- ${err}`).join("\n")}`,
21819
22036
  lines.push(` deps: ${JSON.stringify(t2.dependencies)}`);
21820
22037
  if (t2.qualityGate?.lastTest)
21821
22038
  lines.push(` last test: ${t2.qualityGate.lastTest.status}`);
22039
+ if (t2.shareability)
22040
+ lines.push(` shareable: ${t2.shareability.shareable ? "true" : "false"} - ${t2.shareability.reason}`);
21822
22041
  if (t2.docsPath)
21823
22042
  lines.push(` docs: ${t2.docsPath}`);
21824
22043
  if (t2.analytics) {
@@ -21917,7 +22136,8 @@ ${errors.map((err) => `- ${err}`).join("\n")}`,
21917
22136
  testedAt: (/* @__PURE__ */ new Date()).toISOString(),
21918
22137
  durationMs: result.durationMs,
21919
22138
  outputPreview: truncateText(result.output, 2e3),
21920
- error: result.error
22139
+ error: result.error,
22140
+ schemaValidation: schemaValidationForTest(def, result)
21921
22141
  };
21922
22142
  saveCustomToolDefinition({
21923
22143
  ...def,
@@ -23921,13 +24141,13 @@ var init_structured_file = __esm({
23921
24141
  });
23922
24142
 
23923
24143
  // packages/execution/dist/tools/code-sandbox.js
23924
- import { spawn as spawn6 } from "node:child_process";
24144
+ import { spawn as spawn5 } from "node:child_process";
23925
24145
  import { writeFile as writeFile9, mkdtemp, rm as rm2, readdir as readdir4, stat as stat3 } from "node:fs/promises";
23926
24146
  import { join as join31 } from "node:path";
23927
24147
  import { tmpdir as tmpdir4 } from "node:os";
23928
24148
  function runProcess(cmd, args, options2) {
23929
24149
  return new Promise((resolve57) => {
23930
- const proc = spawn6(cmd, args, {
24150
+ const proc = spawn5(cmd, args, {
23931
24151
  cwd: options2.cwd,
23932
24152
  timeout: options2.timeout,
23933
24153
  env: {
@@ -254977,7 +255197,7 @@ New: ${newNarrative.slice(0, 200)}...`,
254977
255197
  });
254978
255198
 
254979
255199
  // packages/execution/dist/tools/repl.js
254980
- import { spawn as spawn7 } from "node:child_process";
255200
+ import { spawn as spawn6 } from "node:child_process";
254981
255201
  import { createServer as createServer2 } from "node:net";
254982
255202
  import { join as join34 } from "node:path";
254983
255203
  import { tmpdir as tmpdir5 } from "node:os";
@@ -255110,7 +255330,7 @@ except NameError:
255110
255330
  // ── Process lifecycle ────────────────────────────────────────────────────
255111
255331
  async startProcess() {
255112
255332
  await this.startIpcServer();
255113
- this.proc = spawn7("python3", ["-u", "-i", "-q"], {
255333
+ this.proc = spawn6("python3", ["-u", "-i", "-q"], {
255114
255334
  cwd: this.cwd,
255115
255335
  stdio: ["pipe", "pipe", "pipe"],
255116
255336
  env: {
@@ -257281,7 +257501,7 @@ var init_model_store = __esm({
257281
257501
  });
257282
257502
 
257283
257503
  // packages/execution/dist/tools/image-generate.js
257284
- import { spawn as spawn8 } from "node:child_process";
257504
+ import { spawn as spawn7 } from "node:child_process";
257285
257505
  import { existsSync as existsSync29, statSync as statSync11 } from "node:fs";
257286
257506
  import { chmod as chmod3, mkdir as mkdir12, writeFile as writeFile17 } from "node:fs/promises";
257287
257507
  import { join as join40, resolve as resolve19 } from "node:path";
@@ -257522,7 +257742,7 @@ function imageGenerationSetupPlan(backend, repoRoot = ".", model) {
257522
257742
  async function runProcess2(command, args, options2) {
257523
257743
  return new Promise((resolveProcess) => {
257524
257744
  const startedAt2 = Date.now();
257525
- const child = spawn8(command, args, {
257745
+ const child = spawn7(command, args, {
257526
257746
  cwd: options2.cwd,
257527
257747
  env: { ...process.env, ...options2.env },
257528
257748
  stdio: ["ignore", "pipe", "pipe"]
@@ -259325,7 +259545,7 @@ __export(audio_generate_exports, {
259325
259545
  inferAudioGenerationBackend: () => inferAudioGenerationBackend,
259326
259546
  isAudioPresetGated: () => isAudioPresetGated
259327
259547
  });
259328
- import { execFileSync as execFileSync4, spawn as spawn9 } from "node:child_process";
259548
+ import { execFileSync as execFileSync4, spawn as spawn8 } from "node:child_process";
259329
259549
  import { existsSync as existsSync30, readdirSync as readdirSync13, statSync as statSync12 } from "node:fs";
259330
259550
  import { chmod as chmod4, mkdir as mkdir13, writeFile as writeFile18 } from "node:fs/promises";
259331
259551
  import { join as join41 } from "node:path";
@@ -259469,7 +259689,7 @@ function directorySizeBytes(path12) {
259469
259689
  async function runProcess3(command, args, options2) {
259470
259690
  return new Promise((resolveProcess) => {
259471
259691
  const startedAt2 = Date.now();
259472
- const child = spawn9(command, args, {
259692
+ const child = spawn8(command, args, {
259473
259693
  cwd: options2.cwd,
259474
259694
  env: { ...process.env, ...options2.env },
259475
259695
  stdio: ["ignore", "pipe", "pipe"]
@@ -261259,7 +261479,7 @@ if __name__ == "__main__":
261259
261479
  });
261260
261480
 
261261
261481
  // packages/execution/dist/tools/video-generate.js
261262
- import { spawn as spawn10 } from "node:child_process";
261482
+ import { spawn as spawn9 } from "node:child_process";
261263
261483
  import { existsSync as existsSync31, readFileSync as readFileSync22, statSync as statSync13 } from "node:fs";
261264
261484
  import { chmod as chmod5, mkdir as mkdir14, writeFile as writeFile19 } from "node:fs/promises";
261265
261485
  import { homedir as homedir11 } from "node:os";
@@ -261513,7 +261733,7 @@ function videoGenerationSetupPlan(backend, repoRoot = ".", model) {
261513
261733
  async function runProcess4(command, args, options2) {
261514
261734
  return new Promise((resolveProcess) => {
261515
261735
  const startedAt2 = Date.now();
261516
- const child = spawn10(command, args, {
261736
+ const child = spawn9(command, args, {
261517
261737
  cwd: options2.cwd,
261518
261738
  env: { ...process.env, ...options2.env },
261519
261739
  stdio: ["ignore", "pipe", "pipe"]
@@ -261749,7 +261969,7 @@ async function probeComfyAvailable(baseUrl) {
261749
261969
  }
261750
261970
  async function launchComfyBackground(args) {
261751
261971
  const env2 = { ...process.env, PYTHONUNBUFFERED: "1" };
261752
- const child = spawn10("python3", [
261972
+ const child = spawn9("python3", [
261753
261973
  args.bootstrap,
261754
261974
  "--dir",
261755
261975
  args.installDir,
@@ -261882,7 +262102,7 @@ async function muxAudioIntoVideo(args) {
261882
262102
  args.outputPath
261883
262103
  ];
261884
262104
  return await new Promise((resolve57) => {
261885
- const child = spawn10(ffmpegBin(), argv, { stdio: ["ignore", "pipe", "pipe"] });
262105
+ const child = spawn9(ffmpegBin(), argv, { stdio: ["ignore", "pipe", "pipe"] });
261886
262106
  let stderr = "";
261887
262107
  child.stderr?.on("data", (chunk) => {
261888
262108
  stderr += chunk.toString();
@@ -261898,7 +262118,7 @@ async function muxAudioIntoVideo(args) {
261898
262118
  }
261899
262119
  async function ffmpegExtractFirstFrame(videoPath, thumbnailPath) {
261900
262120
  return await new Promise((resolve57) => {
261901
- const child = spawn10(ffmpegBin(), [
262121
+ const child = spawn9(ffmpegBin(), [
261902
262122
  "-hide_banner",
261903
262123
  "-loglevel",
261904
262124
  "error",
@@ -264951,7 +265171,7 @@ __export(vision_exports, {
264951
265171
  resetMoondreamClient: () => resetMoondreamClient
264952
265172
  });
264953
265173
  import { readFileSync as readFileSync23, existsSync as existsSync32, statSync as statSync14, unlinkSync as unlinkSync5, writeFileSync as writeFileSync14 } from "node:fs";
264954
- import { execSync as execSync16, spawn as spawn11, spawnSync as spawnSync4 } from "node:child_process";
265174
+ import { execSync as execSync16, spawn as spawn10, spawnSync as spawnSync4 } from "node:child_process";
264955
265175
  import { resolve as resolve22, extname as extname6, basename as basename7, dirname as dirname9, join as join43 } from "node:path";
264956
265176
  import { fileURLToPath as fileURLToPath4 } from "node:url";
264957
265177
  async function probeStation(endpoint) {
@@ -265023,7 +265243,7 @@ async function autoLaunchStation(port = 2020) {
265023
265243
  if (!existsSync32(launcherScript))
265024
265244
  return false;
265025
265245
  return new Promise((resolvePromise) => {
265026
- const child = spawn11(pythonBin, [launcherScript, "--port", String(port)], {
265246
+ const child = spawn10(pythonBin, [launcherScript, "--port", String(port)], {
265027
265247
  stdio: ["ignore", "pipe", "pipe"]
265028
265248
  });
265029
265249
  stationProcess = child;
@@ -266908,7 +267128,7 @@ Note: Advanced Python pipeline not available — install pytesseract, opencv-pyt
266908
267128
  });
266909
267129
 
266910
267130
  // packages/execution/dist/tools/browser-action.js
266911
- import { execSync as execSync21, spawn as spawn12 } from "node:child_process";
267131
+ import { execSync as execSync21, spawn as spawn11 } from "node:child_process";
266912
267132
  import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync13, readFileSync as readFileSync26 } from "node:fs";
266913
267133
  import { basename as basename11, dirname as dirname11, join as join47, resolve as resolve26 } from "node:path";
266914
267134
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -266956,7 +267176,7 @@ async function launchService() {
266956
267176
  if (!existsSync36(SCRAPE_SCRIPT)) {
266957
267177
  return `Scrape service script not found at ${SCRAPE_SCRIPT}`;
266958
267178
  }
266959
- serviceProcess = spawn12(python, [SCRAPE_SCRIPT], {
267179
+ serviceProcess = spawn11(python, [SCRAPE_SCRIPT], {
266960
267180
  stdio: "ignore",
266961
267181
  env: {
266962
267182
  ...process.env,
@@ -267425,7 +267645,7 @@ var init_browser_action = __esm({
267425
267645
  });
267426
267646
 
267427
267647
  // packages/execution/dist/tools/autoresearch.js
267428
- import { execSync as execSync22, spawn as spawn13 } from "node:child_process";
267648
+ import { execSync as execSync22, spawn as spawn12 } from "node:child_process";
267429
267649
  import { existsSync as existsSync37, readFileSync as readFileSync27, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, appendFileSync, copyFileSync as copyFileSync2 } from "node:fs";
267430
267650
  import { join as join48, resolve as resolve27, dirname as dirname12 } from "node:path";
267431
267651
  import { fileURLToPath as fileURLToPath7 } from "node:url";
@@ -267685,7 +267905,7 @@ Next steps:
267685
267905
  const timeoutMs = timeoutMin * 60 * 1e3;
267686
267906
  const logPath3 = join48(workspace, "run.log");
267687
267907
  return new Promise((resolveResult) => {
267688
- const proc = spawn13("uv", ["run", "train.py"], {
267908
+ const proc = spawn12("uv", ["run", "train.py"], {
267689
267909
  cwd: workspace,
267690
267910
  timeout: timeoutMs,
267691
267911
  stdio: ["pipe", "pipe", "pipe"],
@@ -269708,7 +269928,7 @@ var init_tool_alias = __esm({
269708
269928
  });
269709
269929
 
269710
269930
  // packages/execution/dist/tools/opencode.js
269711
- import { execSync as execSync24, spawn as spawn14 } from "node:child_process";
269931
+ import { execSync as execSync24, spawn as spawn13 } from "node:child_process";
269712
269932
  import { existsSync as existsSync38 } from "node:fs";
269713
269933
  import { join as join52, resolve as resolve31 } from "node:path";
269714
269934
  function findOpencode() {
@@ -269896,7 +270116,7 @@ var init_opencode = __esm({
269896
270116
  let output = "";
269897
270117
  const maxOutput = 5e5;
269898
270118
  let timedOut = false;
269899
- const child = spawn14(binary, cmdArgs, {
270119
+ const child = spawn13(binary, cmdArgs, {
269900
270120
  cwd: dir,
269901
270121
  env: { ...process.env, CI: "true", NONINTERACTIVE: "1" },
269902
270122
  stdio: ["ignore", "pipe", "pipe"]
@@ -269982,7 +270202,7 @@ var init_opencode = __esm({
269982
270202
  });
269983
270203
 
269984
270204
  // packages/execution/dist/tools/factory.js
269985
- import { execSync as execSync25, spawn as spawn15 } from "node:child_process";
270205
+ import { execSync as execSync25, spawn as spawn14 } from "node:child_process";
269986
270206
  import { existsSync as existsSync39 } from "node:fs";
269987
270207
  import { join as join53 } from "node:path";
269988
270208
  function findDroid() {
@@ -270201,7 +270421,7 @@ var init_factory = __esm({
270201
270421
  let output = "";
270202
270422
  const maxOutput = 5e5;
270203
270423
  let timedOut = false;
270204
- const child = spawn15(binary, cmdArgs, {
270424
+ const child = spawn14(binary, cmdArgs, {
270205
270425
  cwd: dir,
270206
270426
  env: { ...process.env },
270207
270427
  stdio: ["ignore", "pipe", "pipe"]
@@ -518845,7 +519065,7 @@ Saved to: ${tempFile}`,
518845
519065
  });
518846
519066
 
518847
519067
  // packages/execution/dist/tools/audio-playback.js
518848
- import { execFileSync as execFileSync5, execSync as execSync30, spawn as spawn16 } from "node:child_process";
519068
+ import { execFileSync as execFileSync5, execSync as execSync30, spawn as spawn15 } from "node:child_process";
518849
519069
  import { copyFileSync as copyFileSync3, existsSync as existsSync45, statSync as statSync22, writeFileSync as writeFileSync18, mkdirSync as mkdirSync19, readdirSync as readdirSync17, writeSync } from "node:fs";
518850
519070
  import { basename as basename14, extname as extname10, isAbsolute as isAbsolute2, join as join62 } from "node:path";
518851
519071
  import { homedir as homedir16, tmpdir as tmpdir11 } from "node:os";
@@ -519413,7 +519633,7 @@ function ensureLuxttsDaemon() {
519413
519633
  }
519414
519634
  finish(false);
519415
519635
  }, 12e4);
519416
- const daemon = spawn16(venvPy, [inferScript], {
519636
+ const daemon = spawn15(venvPy, [inferScript], {
519417
519637
  stdio: ["pipe", "pipe", "pipe"],
519418
519638
  cwd: tmpdir11(),
519419
519639
  env: { ...process.env, LUXTTS_REPO_PATH: repoDir }
@@ -523985,7 +524205,7 @@ print(json.dumps({"ok": False, "error": "No whisper backend available"}))
523985
524205
  });
523986
524206
 
523987
524207
  // packages/execution/dist/tools/full-sub-agent.js
523988
- import { spawn as spawn17, ChildProcess } from "node:child_process";
524208
+ import { spawn as spawn16, ChildProcess } from "node:child_process";
523989
524209
  import { randomBytes as randomBytes17 } from "node:crypto";
523990
524210
  function buildSubProcessArgs(opts) {
523991
524211
  const args = [];
@@ -524026,7 +524246,7 @@ function spawnFullSubAgent(task, opts, onOutput, onComplete) {
524026
524246
  timeoutMs: opts.timeoutMs,
524027
524247
  offline: opts.offline
524028
524248
  });
524029
- const child = spawn17("node", [omniusBin, ...cliArgs], {
524249
+ const child = spawn16("node", [omniusBin, ...cliArgs], {
524030
524250
  cwd: opts.workingDir || process.cwd(),
524031
524251
  env: {
524032
524252
  ...process.env,
@@ -524468,7 +524688,7 @@ var init_agent_tool = __esm({
524468
524688
  })();
524469
524689
  if (isolation === "worktree") {
524470
524690
  this.callbacks.onViewRegister?.(agentId, label);
524471
- const spawn34 = this.callbacks.spawnSubprocess({
524691
+ const spawn33 = this.callbacks.spawnSubprocess({
524472
524692
  id: agentId,
524473
524693
  task: composedPrompt,
524474
524694
  model,
@@ -524478,7 +524698,7 @@ var init_agent_tool = __esm({
524478
524698
  success: true,
524479
524699
  output: `Agent spawned (subprocess, worktree): ${agentId}
524480
524700
  Type: ${subagentType}
524481
- PID: ${spawn34.pid}
524701
+ PID: ${spawn33.pid}
524482
524702
  Task: ${prompt.slice(0, 100)}
524483
524703
  Use task_status("${agentId}") to check progress.`,
524484
524704
  durationMs: performance.now() - start2
@@ -524916,7 +525136,7 @@ var init_send_message = __esm({
524916
525136
  });
524917
525137
 
524918
525138
  // packages/execution/dist/mcp/transport.js
524919
- import { spawn as spawn18 } from "node:child_process";
525139
+ import { spawn as spawn17 } from "node:child_process";
524920
525140
  async function createTransport(config) {
524921
525141
  if (!config.type || config.type === "stdio") {
524922
525142
  const transport = new StdioTransport();
@@ -524948,7 +525168,7 @@ var init_transport5 = __esm({
524948
525168
  async connect(config) {
524949
525169
  const env2 = { ...process.env, ...config.env };
524950
525170
  const args = config.args ?? [];
524951
- this.process = spawn18(config.command, args, {
525171
+ this.process = spawn17(config.command, args, {
524952
525172
  env: env2,
524953
525173
  stdio: ["pipe", "pipe", "pipe"]
524954
525174
  });
@@ -527573,7 +527793,7 @@ Topic: ${segments.slice(0, 5).map((s2) => s2.text).join(" ").slice(0, 300)}`,
527573
527793
 
527574
527794
  // packages/execution/dist/tools/video-scan.js
527575
527795
  import { existsSync as existsSync59, readFileSync as readFileSync44, statSync as statSync25 } from "node:fs";
527576
- import { spawn as spawn19 } from "node:child_process";
527796
+ import { spawn as spawn18 } from "node:child_process";
527577
527797
  import { resolve as resolve36, dirname as dirname18, join as join74, basename as basename16 } from "node:path";
527578
527798
  import { fileURLToPath as fileURLToPath9 } from "node:url";
527579
527799
  import { homedir as homedir25 } from "node:os";
@@ -527618,7 +527838,7 @@ async function autoLaunchSidecar(cfg) {
527618
527838
  if (!launcher)
527619
527839
  return false;
527620
527840
  sidecarLaunchPromise = new Promise((resolveLaunch) => {
527621
- const child = spawn19(venvPython2, [launcher, "--port", String(cfg.sidecar_port)], {
527841
+ const child = spawn18(venvPython2, [launcher, "--port", String(cfg.sidecar_port)], {
527622
527842
  stdio: ["ignore", "pipe", "pipe"],
527623
527843
  env: {
527624
527844
  ...process.env,
@@ -527824,7 +528044,7 @@ Format OK: ${r2.format_ok}`;
527824
528044
 
527825
528045
  // packages/execution/dist/tools/lance.js
527826
528046
  import { existsSync as existsSync60, readFileSync as readFileSync45 } from "node:fs";
527827
- import { spawn as spawn20 } from "node:child_process";
528047
+ import { spawn as spawn19 } from "node:child_process";
527828
528048
  import { resolve as resolve37, dirname as dirname19, join as join75, basename as basename17 } from "node:path";
527829
528049
  import { fileURLToPath as fileURLToPath10 } from "node:url";
527830
528050
  import { homedir as homedir26 } from "node:os";
@@ -527869,7 +528089,7 @@ async function autoLaunchSidecar2(cfg) {
527869
528089
  if (!launcher)
527870
528090
  return false;
527871
528091
  sidecarLaunchPromise2 = new Promise((resolveLaunch) => {
527872
- const child = spawn20(venvPython2, [launcher, "--port", String(cfg.sidecar_port)], {
528092
+ const child = spawn19(venvPython2, [launcher, "--port", String(cfg.sidecar_port)], {
527873
528093
  stdio: ["ignore", "pipe", "pipe"],
527874
528094
  env: {
527875
528095
  ...process.env,
@@ -528204,14 +528424,14 @@ var init_fortemi_bridge = __esm({
528204
528424
  });
528205
528425
 
528206
528426
  // packages/execution/dist/shellRunner.js
528207
- import { spawn as spawn21 } from "node:child_process";
528427
+ import { spawn as spawn20 } from "node:child_process";
528208
528428
  async function runShell(options2) {
528209
528429
  const { command, args = [], cwd: cwd4, env: env2, timeoutMs = DEFAULT_TIMEOUT_MS2 } = options2;
528210
528430
  const mergedEnv = env2 ? { ...process.env, ...env2 } : process.env;
528211
528431
  return new Promise((resolve57) => {
528212
528432
  const start2 = Date.now();
528213
528433
  let timedOut = false;
528214
- const child = spawn21(command, args, {
528434
+ const child = spawn20(command, args, {
528215
528435
  cwd: cwd4,
528216
528436
  env: mergedEnv
528217
528437
  // Do NOT use shell:true — we pass command + args separately
@@ -528310,7 +528530,7 @@ var init_gitWorktree = __esm({
528310
528530
  // packages/execution/dist/patchApplier.js
528311
528531
  import { readFileSync as readFileSync47, writeFileSync as writeFileSync28, existsSync as existsSync62, mkdirSync as mkdirSync30 } from "node:fs";
528312
528532
  import { dirname as dirname20 } from "node:path";
528313
- import { spawn as spawn22 } from "node:child_process";
528533
+ import { spawn as spawn21 } from "node:child_process";
528314
528534
  async function applyPatch(patch) {
528315
528535
  switch (patch.type) {
528316
528536
  case "block-replace":
@@ -528360,7 +528580,7 @@ async function applyUnifiedDiff(patch) {
528360
528580
  function runWithStdin(options2) {
528361
528581
  const { command, args, cwd: cwd4, stdin } = options2;
528362
528582
  return new Promise((resolve57) => {
528363
- const child = spawn22(command, args, {
528583
+ const child = spawn21(command, args, {
528364
528584
  cwd: cwd4,
528365
528585
  stdio: ["pipe", "pipe", "pipe"]
528366
528586
  });
@@ -529087,6 +529307,7 @@ __export(dist_exports, {
529087
529307
  ensureDepsForGroup: () => ensureDepsForGroup,
529088
529308
  ensureDiskSpaceForDownload: () => ensureDiskSpaceForDownload,
529089
529309
  ensureUnifiedCacheDirs: () => ensureUnifiedCacheDirs,
529310
+ evaluateCustomToolShareability: () => evaluateCustomToolShareability,
529090
529311
  evictModelsToFreeSpace: () => evictModelsToFreeSpace,
529091
529312
  extractSkillForQuery: () => extractSkillForQuery,
529092
529313
  fetchWithNetworkEgressPolicy: () => fetchWithNetworkEgressPolicy,
@@ -532057,7 +532278,7 @@ var init_ollama_pool_cleanup = __esm({
532057
532278
  });
532058
532279
 
532059
532280
  // packages/orchestrator/dist/ollama-pool.js
532060
- import { spawn as spawn23, exec as exec2 } from "node:child_process";
532281
+ import { spawn as spawn22, exec as exec2 } from "node:child_process";
532061
532282
  import { existsSync as existsSync65, readFileSync as readFileSync50, readdirSync as readdirSync21, statfsSync as statfsSync3, statSync as statSync26 } from "node:fs";
532062
532283
  import { homedir as homedir28 } from "node:os";
532063
532284
  import { join as join79 } from "node:path";
@@ -532530,7 +532751,7 @@ var init_ollama_pool = __esm({
532530
532751
  env2["CUDA_VISIBLE_DEVICES"] = gpuUuid;
532531
532752
  env2["GPU_DEVICE_ORDINAL"] = gpuIndex === null ? "" : String(gpuIndex);
532532
532753
  }
532533
- const child = spawn23(config.ollamaBinary, ["serve"], {
532754
+ const child = spawn22(config.ollamaBinary, ["serve"], {
532534
532755
  env: env2,
532535
532756
  stdio: ["ignore", "pipe", "pipe"],
532536
532757
  detached: true
@@ -557918,6 +558139,17 @@ ${taskStateStr}
557918
558139
  if (mcpToolNames.length > 0) {
557919
558140
  postCompactRestore.push(`Available MCP tools: ${mcpToolNames.join(", ")}`);
557920
558141
  }
558142
+ const activeCustomTools = [...this.tools.values()].map((tool) => ({ tool, meta: tool.customToolMetadata })).filter(({ tool, meta }) => meta?.isCustomTool === true && (this._activatedTools.has(tool.name) || meta.qualityGate?.lastTest?.status === "passed")).slice(0, 8);
558143
+ if (activeCustomTools.length > 0) {
558144
+ postCompactRestore.push([
558145
+ "Active custom tools:",
558146
+ ...activeCustomTools.map(({ tool, meta }) => {
558147
+ const last2 = meta.qualityGate?.lastTest;
558148
+ const failure = last2?.status === "failed" && last2.error ? `, last failure=${String(last2.error).slice(0, 120)}` : "";
558149
+ return `- ${tool.name} v${meta.version ?? 1}: docs=${meta.docsPath ?? "missing"}, lastTest=${last2?.status ?? "untested"}${failure}, patch=manage_tools(action="patch", tool_name="${tool.name}", patch={...}, test_args={...}), test=manage_tools(action="test", tool_name="${tool.name}", test_args={...})`;
558150
+ })
558151
+ ].join("\n"));
558152
+ }
557921
558153
  if (postCompactRestore.length > 0) {
557922
558154
  enrichments.push(`[Post-compaction context restore]
557923
558155
  ${postCompactRestore.join("\n")}`);
@@ -559347,10 +559579,49 @@ ${transcript}`
559347
559579
  }
559348
559580
  }
559349
559581
  const getDesc = (tool) => dynamicDescs.get(tool.name) ?? tool.description;
559582
+ const getCustomToolMetadata = (tool) => {
559583
+ const meta = tool.customToolMetadata;
559584
+ return meta?.isCustomTool === true ? meta : void 0;
559585
+ };
559586
+ const customToolSearchText = (tool) => {
559587
+ const meta = getCustomToolMetadata(tool);
559588
+ if (!meta)
559589
+ return "";
559590
+ return JSON.stringify({
559591
+ docsPath: meta.docsPath,
559592
+ qualityGate: meta.qualityGate,
559593
+ dependencies: meta.dependencies,
559594
+ examples: meta.examples,
559595
+ analytics: meta.analytics,
559596
+ shareability: meta.shareability
559597
+ });
559598
+ };
559599
+ const customToolDetails = (tool) => {
559600
+ const meta = getCustomToolMetadata(tool);
559601
+ if (!meta)
559602
+ return "";
559603
+ const last2 = meta.qualityGate?.lastTest;
559604
+ const example = Array.isArray(meta.examples) && meta.examples[0] ? `
559605
+ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
559606
+ return [
559607
+ "",
559608
+ `Custom tool: v${meta.version ?? 1}`,
559609
+ `Quality gate: ${last2?.status ?? "untested"}${last2?.schemaValidation?.status ? `, schema=${last2.schemaValidation.status}` : ""}`,
559610
+ meta.docsPath ? `Docs: ${meta.docsPath}` : "Docs: [missing]",
559611
+ meta.analytics?.runs ? `Usage: ${meta.analytics.runs} run(s), ${Math.round(meta.analytics.successes / Math.max(1, meta.analytics.runs) * 100)}% success` : "",
559612
+ meta.shareability ? `Shareable: ${meta.shareability.shareable ? "true" : "false"} - ${meta.shareability.reason ?? ""}` : "",
559613
+ example
559614
+ ].filter(Boolean).join("\n");
559615
+ };
559350
559616
  const getIndexLabel = (tool) => {
559351
- const desc = getDesc(tool).toLowerCase().replace(/[`"'()[\]{}:;,.!?/\\|-]+/g, " ");
559617
+ const meta = getCustomToolMetadata(tool);
559618
+ const desc = `${getDesc(tool)} ${customToolSearchText(tool)}`.toLowerCase().replace(/[`"'()[\]{}:;,.!?/\\|-]+/g, " ");
559352
559619
  const keywords2 = Array.from(new Set(desc.split(/\s+/).filter((word2) => word2.length > 2 && !STOPWORDS3.has(word2) && !tool.name.toLowerCase().includes(word2)))).slice(0, 4);
559353
- return keywords2.length > 0 ? `${tool.name}(${keywords2.join(",")})` : tool.name;
559620
+ const base3 = keywords2.length > 0 ? `${tool.name}(${keywords2.join(",")})` : tool.name;
559621
+ if (!meta)
559622
+ return base3;
559623
+ const status = meta.qualityGate?.lastTest?.status ?? "untested";
559624
+ return `${base3}:custom:${status}`;
559354
559625
  };
559355
559626
  const CORE_TOOLS3 = /* @__PURE__ */ new Set([
559356
559627
  "file_read",
@@ -559375,7 +559646,8 @@ ${transcript}`
559375
559646
  for (const tool of allTools) {
559376
559647
  if (CORE_TOOLS3.has(tool.name))
559377
559648
  continue;
559378
- const toolText = `${tool.name} ${getDesc(tool)}`.toLowerCase();
559649
+ const customMeta = getCustomToolMetadata(tool);
559650
+ const toolText = `${tool.name} ${getDesc(tool)} ${customToolSearchText(tool)}`.toLowerCase();
559379
559651
  const toolWords = toolText.split(/\s+/).filter((w) => w.length > 2);
559380
559652
  let score = 0;
559381
559653
  for (const tw of toolWords) {
@@ -559387,7 +559659,26 @@ ${transcript}`
559387
559659
  }
559388
559660
  }
559389
559661
  if (taskText.includes(tool.name.replace(/_/g, " ")) || taskText.includes(tool.name)) {
559390
- score += 10;
559662
+ score += customMeta ? 16 : 10;
559663
+ }
559664
+ if (customMeta) {
559665
+ const lastStatus = customMeta.qualityGate?.lastTest?.status;
559666
+ if (lastStatus === "passed")
559667
+ score += 6;
559668
+ else if (lastStatus)
559669
+ score -= 4;
559670
+ if (customMeta.analytics?.runs > 0) {
559671
+ score += Math.round(customMeta.analytics.successes / Math.max(1, customMeta.analytics.runs) * 4);
559672
+ if (customMeta.analytics.lastRunAt)
559673
+ score += 1;
559674
+ }
559675
+ if (customMeta.shareability?.shareable)
559676
+ score += 2;
559677
+ const docsExamplesDeps = customToolSearchText(tool).toLowerCase();
559678
+ for (const word2 of taskWords) {
559679
+ if (docsExamplesDeps.includes(word2))
559680
+ score += 2;
559681
+ }
559391
559682
  }
559392
559683
  if ([
559393
559684
  "explore_tools",
@@ -559511,7 +559802,7 @@ ${catalog}`,
559511
559802
  continue;
559512
559803
  lines.push("");
559513
559804
  lines.push(`## ${tool.name}`);
559514
- lines.push(getDesc(tool));
559805
+ lines.push(`${getDesc(tool)}${customToolDetails(tool)}`);
559515
559806
  lines.push(`Parameters: ${JSON.stringify(tool.parameters)}`);
559516
559807
  }
559517
559808
  }
@@ -559523,7 +559814,32 @@ ${catalog}`,
559523
559814
  }
559524
559815
  return { success: true, output: lines.join("\n") };
559525
559816
  }
559526
- const matches = deferred.filter((t2) => t2.name.toLowerCase().includes(query) || getDesc(t2).toLowerCase().includes(query)).slice(0, 5);
559817
+ const matches = deferred.filter((t2) => t2.name.toLowerCase().includes(query) || getDesc(t2).toLowerCase().includes(query) || customToolSearchText(t2).toLowerCase().includes(query)).sort((a2, b) => {
559818
+ const scoreTool = (tool) => {
559819
+ const meta = getCustomToolMetadata(tool);
559820
+ let score = 0;
559821
+ if (tool.name.toLowerCase() === query)
559822
+ score += 30;
559823
+ if (tool.name.toLowerCase().includes(query))
559824
+ score += 10;
559825
+ if (getDesc(tool).toLowerCase().includes(query))
559826
+ score += 4;
559827
+ if (customToolSearchText(tool).toLowerCase().includes(query))
559828
+ score += 6;
559829
+ if (meta?.qualityGate?.lastTest?.status === "passed")
559830
+ score += 6;
559831
+ else if (meta)
559832
+ score -= 4;
559833
+ const analytics = meta?.analytics;
559834
+ if (analytics?.runs > 0) {
559835
+ score += Math.round(analytics.successes / Math.max(1, analytics.runs) * 4);
559836
+ if (analytics.lastRunAt)
559837
+ score += 1;
559838
+ }
559839
+ return score;
559840
+ };
559841
+ return scoreTool(b) - scoreTool(a2);
559842
+ }).slice(0, 5);
559527
559843
  if (matches.length === 0) {
559528
559844
  const subsetHint = Object.keys(subsetCatalog).join(", ");
559529
559845
  return {
@@ -559537,7 +559853,7 @@ ${catalog}`,
559537
559853
  const result = matches.map((t2) => {
559538
559854
  const paramsStr = JSON.stringify(t2.parameters, null, 2);
559539
559855
  return `## ${t2.name}
559540
- ${getDesc(t2)}
559856
+ ${getDesc(t2)}${customToolDetails(t2)}
559541
559857
 
559542
559858
  Parameters:
559543
559859
  ${paramsStr}`;
@@ -565552,7 +565868,7 @@ __export(listen_exports, {
565552
565868
  isVideoPath: () => isVideoPath,
565553
565869
  waitForTranscribeCli: () => waitForTranscribeCli
565554
565870
  });
565555
- import { spawn as spawn24, execSync as execSync47 } from "node:child_process";
565871
+ import { spawn as spawn23, execSync as execSync47 } from "node:child_process";
565556
565872
  import { accessSync, constants, existsSync as existsSync88, mkdirSync as mkdirSync48, writeFileSync as writeFileSync43, readdirSync as readdirSync28 } from "node:fs";
565557
565873
  import { join as join103, dirname as dirname27 } from "node:path";
565558
565874
  import { homedir as homedir32 } from "node:os";
@@ -565674,7 +565990,7 @@ async function transcribeFileViaWhisper(filePath, model) {
565674
565990
  const venvPython2 = join103(homedir32(), ".omnius", "venv", bin, exe);
565675
565991
  if (!existsSync88(venvPython2)) return null;
565676
565992
  return new Promise((resolve57) => {
565677
- const child = spawn24(venvPython2, [script], {
565993
+ const child = spawn23(venvPython2, [script], {
565678
565994
  stdio: ["pipe", "pipe", "pipe"],
565679
565995
  env: process.env
565680
565996
  });
@@ -565811,7 +566127,7 @@ function ensureTranscribeCliBackground() {
565811
566127
  const command = terminalElevation && needsSudo ? "sudo" : "npm";
565812
566128
  const args = terminalElevation && needsSudo ? ["npm", "i", "-g", "transcribe-cli"] : ["i", "-g", "transcribe-cli"];
565813
566129
  return new Promise((resolve57) => {
565814
- const child = spawn24(command, args, {
566130
+ const child = spawn23(command, args, {
565815
566131
  stdio: terminalElevation ? "inherit" : "ignore",
565816
566132
  timeout: 18e4
565817
566133
  });
@@ -565907,7 +566223,7 @@ var init_listen = __esm({
565907
566223
  }
565908
566224
  } catch {
565909
566225
  }
565910
- this.process = spawn24(pyPath, [
566226
+ this.process = spawn23(pyPath, [
565911
566227
  this.scriptPath,
565912
566228
  "--model",
565913
566229
  this.model,
@@ -566238,7 +566554,7 @@ transcribe-cli error: ${transcribeCliError}` : "";
566238
566554
  return `Failed to start live transcription: ${msg}${tcHint}`;
566239
566555
  }
566240
566556
  }
566241
- this.micProcess = spawn24(micCmd.cmd, micCmd.args, {
566557
+ this.micProcess = spawn23(micCmd.cmd, micCmd.args, {
566242
566558
  stdio: ["pipe", "pipe", "pipe"],
566243
566559
  env: { ...process.env }
566244
566560
  });
@@ -566351,7 +566667,7 @@ transcribe-cli error: ${transcribeCliError}` : "";
566351
566667
  if (!micCmd) {
566352
566668
  return "No microphone capture tool found.";
566353
566669
  }
566354
- this.micProcess = spawn24(micCmd.cmd, micCmd.args, {
566670
+ this.micProcess = spawn23(micCmd.cmd, micCmd.args, {
566355
566671
  stdio: ["pipe", "pipe", "pipe"],
566356
566672
  env: { ...process.env }
566357
566673
  });
@@ -573737,7 +574053,7 @@ var init_render = __esm({
573737
574053
 
573738
574054
  // packages/cli/src/tui/voice-session.ts
573739
574055
  import { createServer as createServer4 } from "node:http";
573740
- import { spawn as spawn25, execSync as execSync49 } from "node:child_process";
574056
+ import { spawn as spawn24, execSync as execSync49 } from "node:child_process";
573741
574057
  import { EventEmitter as EventEmitter7 } from "node:events";
573742
574058
  function generateFrontendHTML() {
573743
574059
  return `<!DOCTYPE html>
@@ -574782,7 +575098,7 @@ var init_voice_session = __esm({
574782
575098
  const timeout2 = setTimeout(() => {
574783
575099
  reject(new Error("Cloudflared tunnel start timeout (30s)"));
574784
575100
  }, 3e4);
574785
- this.cloudflaredProcess = spawn25("cloudflared", [
575101
+ this.cloudflaredProcess = spawn24("cloudflared", [
574786
575102
  "tunnel",
574787
575103
  "--url",
574788
575104
  `http://127.0.0.1:${port}`
@@ -575659,7 +575975,7 @@ var init_usage_bars = __esm({
575659
575975
  // packages/cli/src/tui/expose.ts
575660
575976
  import { createServer as createServer5, request as httpRequest } from "node:http";
575661
575977
  import { request as httpsRequest } from "node:https";
575662
- import { spawn as spawn26, exec as exec3 } from "node:child_process";
575978
+ import { spawn as spawn25, exec as exec3 } from "node:child_process";
575663
575979
  import { EventEmitter as EventEmitter8 } from "node:events";
575664
575980
  import { randomBytes as randomBytes20, timingSafeEqual } from "node:crypto";
575665
575981
  import { URL as URL2 } from "node:url";
@@ -576819,7 +577135,7 @@ var init_expose = __esm({
576819
577135
  clearTimeout(timeout2);
576820
577136
  clearInterval(progressInterval);
576821
577137
  };
576822
- this.cloudflaredProcess = spawn26("cloudflared", [
577138
+ this.cloudflaredProcess = spawn25("cloudflared", [
576823
577139
  "tunnel",
576824
577140
  "--url",
576825
577141
  `http://127.0.0.1:${port}`,
@@ -586814,11 +587130,11 @@ __export(personaplex_exports, {
586814
587130
  import { existsSync as existsSync97, writeFileSync as writeFileSync49, readFileSync as readFileSync78, mkdirSync as mkdirSync54, copyFileSync as copyFileSync4, readdirSync as readdirSync32, statSync as statSync37 } from "node:fs";
586815
587131
  import { join as join113, dirname as dirname32 } from "node:path";
586816
587132
  import { homedir as homedir36 } from "node:os";
586817
- import { execSync as execSync50, spawn as spawn27 } from "node:child_process";
587133
+ import { execSync as execSync50, spawn as spawn26 } from "node:child_process";
586818
587134
  import { fileURLToPath as fileURLToPath15 } from "node:url";
586819
587135
  function execAsync(cmd, opts = {}) {
586820
587136
  return new Promise((resolve57, reject) => {
586821
- const child = spawn27("bash", ["-c", cmd], {
587137
+ const child = spawn26("bash", ["-c", cmd], {
586822
587138
  stdio: ["ignore", "pipe", "pipe"],
586823
587139
  timeout: opts.timeout ?? 3e5,
586824
587140
  env: opts.env ?? process.env
@@ -587454,7 +587770,7 @@ print('Converted')
587454
587770
  serverEnv["HYBRID_LLM_MODEL"] = ollamaModel;
587455
587771
  serverEnv["HYBRID_MODEL_FAST"] = "qwen3.5:4b";
587456
587772
  }
587457
- const child = spawn27(venvPython2, serverArgs, {
587773
+ const child = spawn26(venvPython2, serverArgs, {
587458
587774
  stdio: ["ignore", "pipe", "pipe"],
587459
587775
  detached: true,
587460
587776
  env: serverEnv,
@@ -587583,7 +587899,7 @@ async function clonePersonaPlexVoice(inputWav, voiceName, onInfo) {
587583
587899
  log22(`Cloning voice "${voiceName}" from ${inputWav}...`);
587584
587900
  log22("This requires loading the full 7B model — may take 30-60s...");
587585
587901
  return new Promise((resolve57) => {
587586
- const child = spawn27(venvPython2, [
587902
+ const child = spawn26(venvPython2, [
587587
587903
  cloneScript,
587588
587904
  "--input",
587589
587905
  inputWav,
@@ -587820,7 +588136,7 @@ __export(setup_exports, {
587820
588136
  updateOllama: () => updateOllama
587821
588137
  });
587822
588138
  import * as readline from "node:readline";
587823
- import { execSync as execSync51, spawn as spawn28, exec as exec5 } from "node:child_process";
588139
+ import { execSync as execSync51, spawn as spawn27, exec as exec5 } from "node:child_process";
587824
588140
  import { promisify as promisify6 } from "node:util";
587825
588141
  import { existsSync as existsSync98, writeFileSync as writeFileSync50, readFileSync as readFileSync79, appendFileSync as appendFileSync7, mkdirSync as mkdirSync55 } from "node:fs";
587826
588142
  import { join as join114 } from "node:path";
@@ -588679,7 +588995,7 @@ async function ensureOllamaRunning(backendUrl2, rl) {
588679
588995
  process.stdout.write(` ${c3.cyan("●")} Starting ollama serve...
588680
588996
  `);
588681
588997
  try {
588682
- const child = spawn28("ollama", ["serve"], { stdio: "ignore", detached: true });
588998
+ const child = spawn27("ollama", ["serve"], { stdio: "ignore", detached: true });
588683
588999
  child.unref();
588684
589000
  } catch {
588685
589001
  process.stdout.write(` ${c3.cyan("⚠")} Could not start ollama serve.
@@ -589360,7 +589676,7 @@ ${c3.cyan(OMNIUS_FIRST_RUN_BANNER)}
589360
589676
  ${c3.cyan("●")} Ollama is installed but not running. Starting automatically...
589361
589677
  `);
589362
589678
  try {
589363
- const child = spawn28("ollama", ["serve"], { stdio: "ignore", detached: true });
589679
+ const child = spawn27("ollama", ["serve"], { stdio: "ignore", detached: true });
589364
589680
  child.unref();
589365
589681
  await new Promise((resolve57) => setTimeout(resolve57, 3e3));
589366
589682
  try {
@@ -589388,7 +589704,7 @@ ${c3.cyan(OMNIUS_FIRST_RUN_BANNER)}
589388
589704
  ${c3.cyan("●")} Starting ollama serve...
589389
589705
  `);
589390
589706
  try {
589391
- const child = spawn28("ollama", ["serve"], { stdio: "ignore", detached: true });
589707
+ const child = spawn27("ollama", ["serve"], { stdio: "ignore", detached: true });
589392
589708
  child.unref();
589393
589709
  await new Promise((resolve57) => setTimeout(resolve57, 3e3));
589394
589710
  try {
@@ -594546,7 +594862,7 @@ __export(daemon_exports, {
594546
594862
  startDaemon: () => startDaemon,
594547
594863
  stopDaemon: () => stopDaemon
594548
594864
  });
594549
- import { spawn as spawn29 } from "node:child_process";
594865
+ import { spawn as spawn28 } from "node:child_process";
594550
594866
  import { existsSync as existsSync104, readFileSync as readFileSync82, writeFileSync as writeFileSync51, mkdirSync as mkdirSync56, unlinkSync as unlinkSync19, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
594551
594867
  import { join as join118 } from "node:path";
594552
594868
  import { homedir as homedir39 } from "node:os";
@@ -594637,7 +594953,7 @@ async function startDaemon() {
594637
594953
  try {
594638
594954
  outFd = openSync3(join118(OMNIUS_DIR2, "daemon.log"), "a");
594639
594955
  errFd = openSync3(join118(OMNIUS_DIR2, "daemon.err.log"), "a");
594640
- const child = spawn29(daemonCommand.command, daemonCommand.args, {
594956
+ const child = spawn28(daemonCommand.command, daemonCommand.args, {
594641
594957
  detached: true,
594642
594958
  stdio: ["ignore", outFd, errFd],
594643
594959
  env: {
@@ -600418,7 +600734,7 @@ async function acquireSudoCredentials(ctx3, reason) {
600418
600734
  }
600419
600735
  async function runSudoScript(ctx3, script) {
600420
600736
  try {
600421
- const { spawn: spawn34 } = await import("node:child_process");
600737
+ const { spawn: spawn33 } = await import("node:child_process");
600422
600738
  const full = `set -e; ${script}`;
600423
600739
  await withTransientTerminalPrivilegePrompt(
600424
600740
  ctx3,
@@ -600430,7 +600746,7 @@ async function runSudoScript(ctx3, script) {
600430
600746
  );
600431
600747
  const cmd = isRoot ? "bash" : "sudo";
600432
600748
  const args = isRoot ? ["-lc", full] : hasInteractiveTty ? ["bash", "-lc", full] : ["-n", "bash", "-lc", full];
600433
- const child = spawn34(cmd, args, {
600749
+ const child = spawn33(cmd, args, {
600434
600750
  stdio: hasInteractiveTty ? "inherit" : ["ignore", "pipe", "pipe"],
600435
600751
  env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
600436
600752
  });
@@ -602871,8 +603187,8 @@ async function handleSlashCommand(input, ctx3) {
602871
603187
  writeFileSync54(jwtFile, JSON.stringify(jwtPayload, null, 2));
602872
603188
  renderInfo(`Launching fortemi-react from ${fDir}...`);
602873
603189
  try {
602874
- const { spawn: spawn34 } = __require("node:child_process");
602875
- const child = spawn34(
603190
+ const { spawn: spawn33 } = __require("node:child_process");
603191
+ const child = spawn33(
602876
603192
  "npx",
602877
603193
  ["vite", "dev", "--host", "0.0.0.0", "--port", "3000"],
602878
603194
  {
@@ -603878,9 +604194,9 @@ systemctl --user daemon-reload || true
603878
604194
  systemctl --user enable --now omnius-daemon.service || true
603879
604195
  sleep 1
603880
604196
  `;
603881
- const { spawn: spawn34 } = await import("node:child_process");
604197
+ const { spawn: spawn33 } = await import("node:child_process");
603882
604198
  await new Promise((resolve57) => {
603883
- const child = spawn34("bash", ["-lc", takeover], {
604199
+ const child = spawn33("bash", ["-lc", takeover], {
603884
604200
  stdio: "inherit"
603885
604201
  });
603886
604202
  onChildExit(child, () => resolve57());
@@ -611367,8 +611683,8 @@ ${escapedContent}EOF'`, {
611367
611683
  }
611368
611684
  await new Promise((r2) => setTimeout(r2, 1e3));
611369
611685
  process.env.OLLAMA_NUM_PARALLEL = String(n2);
611370
- const { spawn: spawn34 } = await import("node:child_process");
611371
- const child = spawn34("ollama", ["serve"], {
611686
+ const { spawn: spawn33 } = await import("node:child_process");
611687
+ const child = spawn33("ollama", ["serve"], {
611372
611688
  stdio: "ignore",
611373
611689
  detached: true,
611374
611690
  env: { ...process.env, OLLAMA_NUM_PARALLEL: String(n2) }
@@ -612248,7 +612564,7 @@ async function handleUpdate(subcommand, ctx3) {
612248
612564
  }
612249
612565
  };
612250
612566
  }
612251
- const { exec: exec6, spawn: spawn34, execSync: es2 } = await import("node:child_process");
612567
+ const { exec: exec6, spawn: spawn33, execSync: es2 } = await import("node:child_process");
612252
612568
  const execA = (cmd, opts) => new Promise(
612253
612569
  (res, rej) => exec6(
612254
612570
  cmd,
@@ -612663,7 +612979,7 @@ async function handleUpdate(subcommand, ctx3) {
612663
612979
  }
612664
612980
  }, 1e3);
612665
612981
  heartbeat.unref?.();
612666
- const child = spawn34(cmd, {
612982
+ const child = spawn33(cmd, {
612667
612983
  shell: true,
612668
612984
  stdio: ["ignore", "pipe", "pipe"],
612669
612985
  env: {
@@ -614121,12 +614437,74 @@ function loadPatternSuggestions(repoRoot, store2) {
614121
614437
  for (const p2 of all2.slice(0, 3)) {
614122
614438
  const steps = p2.pattern.map((e2) => e2.tool).join(" → ");
614123
614439
  lines.push(`- [${p2.occurrences}x, ${p2.scope}] ${steps}`);
614440
+ const candidate = buildToolCreationCandidate(p2);
614441
+ if (candidate) {
614442
+ lines.push(` Candidate: ${candidate}`);
614443
+ }
614124
614444
  }
614125
614445
  return lines.join("\n");
614126
614446
  } catch {
614127
614447
  return "";
614128
614448
  }
614129
614449
  }
614450
+ function buildToolCreationCandidate(pattern) {
614451
+ const tools = pattern.pattern.map((entry) => entry.tool);
614452
+ const toolName = sanitizeToolName(`auto_${tools.join("_")}`).slice(0, 64).replace(/_+$/g, "") || "auto_workflow";
614453
+ const params = Array.from(new Set(pattern.pattern.flatMap((entry) => entry.argKeys))).filter(Boolean).slice(0, 8);
614454
+ const properties = Object.fromEntries(params.map((param) => [param, { type: "string", description: `Value observed for ${param}` }]));
614455
+ const steps = pattern.pattern.map((entry, index) => ({
614456
+ description: `Run observed ${entry.tool} step ${index + 1}`,
614457
+ tool: entry.tool,
614458
+ args: Object.fromEntries(entry.argKeys.map((key) => [key, `{{${key}}}`]))
614459
+ }));
614460
+ const testArgs = Object.fromEntries(params.map((param) => [param, `<${param}>`]));
614461
+ return [
614462
+ `create_tool(tool_name="${toolName}", scope="${pattern.scope}", `,
614463
+ `tool_parameters=${JSON.stringify({ type: "object", properties, required: params })}, `,
614464
+ `steps=${JSON.stringify(steps)}, test_args=${JSON.stringify(testArgs)})`
614465
+ ].join("");
614466
+ }
614467
+ function sanitizeToolName(value2) {
614468
+ const cleaned = value2.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+/, "");
614469
+ return /^[a-z]/.test(cleaned) ? cleaned : `tool_${cleaned}`;
614470
+ }
614471
+ function loadCustomToolsContext(repoRoot) {
614472
+ try {
614473
+ const registryPath = join122(repoRoot, ".omnius", "tools", "registry.json");
614474
+ const indexPath = join122(repoRoot, ".omnius", "tools", "README.md");
614475
+ if (!existsSync109(registryPath)) return "";
614476
+ const registry4 = JSON.parse(readFileSync87(registryPath, "utf-8"));
614477
+ const tools = (registry4.tools ?? []).filter((tool) => tool.qualityGate?.lastTest?.status === "passed").sort((a2, b) => {
614478
+ const byRate = (b.analytics?.successRate ?? 0) - (a2.analytics?.successRate ?? 0);
614479
+ if (byRate !== 0) return byRate;
614480
+ return String(b.analytics?.lastRunAt ?? "").localeCompare(String(a2.analytics?.lastRunAt ?? ""));
614481
+ });
614482
+ if (tools.length === 0) return "";
614483
+ return tools.map((tool) => {
614484
+ const example = tool.examples?.[0];
614485
+ const successRate = tool.analytics?.successRate !== void 0 ? `${Math.round(tool.analytics.successRate * 100)}%` : "n/a";
614486
+ const tested = tool.qualityGate?.lastTest?.testedAt?.split("T")[0] ?? "passed";
614487
+ const schema = tool.qualityGate?.lastTest?.schemaValidation?.status;
614488
+ const bits = [
614489
+ `- ${tool.name} v${tool.version ?? 1}: ${String(tool.description ?? "").slice(0, 140)}`,
614490
+ `docs=${tool.docsPath ?? "missing"}`,
614491
+ existsSync109(indexPath) ? `index=${indexPath}` : "",
614492
+ `test=${tested}${schema ? `, schema=${schema}` : ""}`,
614493
+ `success=${successRate}`,
614494
+ example?.args ? `example=${tool.name}(${JSON.stringify(example.args)})` : "",
614495
+ tool.shareability ? `shareable=${tool.shareability.shareable ? "true" : "false"}:${tool.shareability.reason ?? ""}` : ""
614496
+ ].filter(Boolean);
614497
+ return bits.join(" | ");
614498
+ }).join("\n");
614499
+ } catch {
614500
+ return "";
614501
+ }
614502
+ }
614503
+ function limitCustomToolsContext(context2, modelTier) {
614504
+ if (!context2) return "";
614505
+ const limit = modelTier === "small" ? 5 : modelTier === "medium" ? 10 : 20;
614506
+ return context2.split("\n").filter(Boolean).slice(0, limit).join("\n");
614507
+ }
614130
614508
  function buildProjectContext(repoRoot, stores) {
614131
614509
  const skills = discoverSkills(repoRoot);
614132
614510
  writeCompressedSkillsArtifact(repoRoot, skills);
@@ -614140,6 +614518,7 @@ function buildProjectContext(repoRoot, stores) {
614140
614518
  taskMemories: stores?.taskMemoryStore ? loadTaskMemories(repoRoot, stores.taskMemoryStore) : "",
614141
614519
  failurePatterns: stores?.failureStore ? loadFailurePatterns(stores.failureStore) : "",
614142
614520
  patternSuggestions: stores?.toolPatternStore ? loadPatternSuggestions(repoRoot, stores.toolPatternStore) : "",
614521
+ customToolsContext: loadCustomToolsContext(repoRoot),
614143
614522
  skillsSummary: buildSkillsSummary(skills)
614144
614523
  };
614145
614524
  }
@@ -614227,6 +614606,14 @@ ${ctx3.sessionHistory}`);
614227
614606
  ${ctx3.failurePatterns}
614228
614607
 
614229
614608
  Avoid approaches that led to these failures. If you encounter these errors, try a different strategy.`);
614609
+ }
614610
+ const customTools = limitCustomToolsContext(ctx3.customToolsContext, modelTier);
614611
+ if (customTools) {
614612
+ sections.push(`## Tested Custom Tools
614613
+
614614
+ ${customTools}
614615
+
614616
+ These tools have passed their smoke test and are available as normal function tools. Use tool_search by tool name for full schema if deferred.`);
614230
614617
  }
614231
614618
  if (modelTier === "large" && ctx3.patternSuggestions) {
614232
614619
  sections.push(`## Tool Creation Suggestions
@@ -641705,7 +642092,7 @@ __export(graphical_sudo_exports, {
641705
642092
  detectSudoHelper: () => detectSudoHelper,
641706
642093
  runGraphicalSudo: () => runGraphicalSudo
641707
642094
  });
641708
- import { spawn as spawn30 } from "node:child_process";
642095
+ import { spawn as spawn29 } from "node:child_process";
641709
642096
  import { existsSync as existsSync130, mkdirSync as mkdirSync76, writeFileSync as writeFileSync68, chmodSync as chmodSync4 } from "node:fs";
641710
642097
  import { join as join143 } from "node:path";
641711
642098
  import { tmpdir as tmpdir21 } from "node:os";
@@ -641775,7 +642162,7 @@ async function runGraphicalSudo(opts) {
641775
642162
  args = ["/bin/bash", opts.scriptPath, ...opts.args ?? []];
641776
642163
  }
641777
642164
  return new Promise((resolve57, reject) => {
641778
- const child = spawn30(cmd, args, {
642165
+ const child = spawn29(cmd, args, {
641779
642166
  env: { ...process.env, ...opts.env || {}, ...extraEnv },
641780
642167
  stdio: ["ignore", "pipe", "pipe"]
641781
642168
  });
@@ -654499,7 +654886,7 @@ var init_profiles = __esm({
654499
654886
  });
654500
654887
 
654501
654888
  // packages/cli/src/docker.ts
654502
- import { execSync as execSync57, spawn as spawn31 } from "node:child_process";
654889
+ import { execSync as execSync57, spawn as spawn30 } from "node:child_process";
654503
654890
  import { existsSync as existsSync134, mkdirSync as mkdirSync79, writeFileSync as writeFileSync71 } from "node:fs";
654504
654891
  import { join as join147, resolve as resolve51, dirname as dirname40 } from "node:path";
654505
654892
  import { homedir as homedir52 } from "node:os";
@@ -654780,7 +655167,7 @@ function runInContainer(opts) {
654780
655167
  if (opts.maxTurns) omniusArgs.push("--max-turns", String(opts.maxTurns));
654781
655168
  if (opts.timeoutS) omniusArgs.push("--timeout", String(opts.timeoutS));
654782
655169
  args.push(...omniusArgs);
654783
- return spawn31("docker", args, {
655170
+ return spawn30("docker", args, {
654784
655171
  stdio: ["ignore", "pipe", "pipe"]
654785
655172
  });
654786
655173
  }
@@ -654983,7 +655370,7 @@ import { createRequire as createRequire7 } from "node:module";
654983
655370
  import { fileURLToPath as fileURLToPath19 } from "node:url";
654984
655371
  import { dirname as dirname41, join as join149, resolve as resolve52 } from "node:path";
654985
655372
  import { homedir as homedir53 } from "node:os";
654986
- import { spawn as spawn32, execSync as execSync58 } from "node:child_process";
655373
+ import { spawn as spawn31, execSync as execSync58 } from "node:child_process";
654987
655374
  import { mkdirSync as mkdirSync80, writeFileSync as writeFileSync72, readFileSync as readFileSync109, readdirSync as readdirSync48, existsSync as existsSync135, watch as fsWatch4, renameSync as renameSync9, unlinkSync as unlinkSync28 } from "node:fs";
654988
655375
  import { randomBytes as randomBytes27, randomUUID as randomUUID17 } from "node:crypto";
654989
655376
  import { createHash as createHash34 } from "node:crypto";
@@ -657357,7 +657744,7 @@ ${task}` : task;
657357
657744
  runEnv["OMNIUS_RUN_SCOPE"] = req2._authScope || "admin";
657358
657745
  runEnv["OLLAMA_HOST"] = currentCfg.backendUrl || process.env["OLLAMA_HOST"] || "http://127.0.0.1:11434";
657359
657746
  if (currentCfg.apiKey) runEnv["OMNIUS_API_KEY_INHERIT"] = currentCfg.apiKey;
657360
- const child = spawn32(process.execPath, [omniusBin, ...args], {
657747
+ const child = spawn31(process.execPath, [omniusBin, ...args], {
657361
657748
  cwd: resolve52(process.cwd()),
657362
657749
  env: runEnv,
657363
657750
  stdio: ["ignore", "pipe", "pipe"]
@@ -657686,7 +658073,7 @@ async function handleV1Update(req2, res, requestId) {
657686
658073
  cleanEnv.PATH = pathParts.join(":");
657687
658074
  let child;
657688
658075
  if (isWin2) {
657689
- child = spawn32(npmBin, ["install", "-g", pkgSpec, "--no-audit", "--no-fund", "--no-progress"], {
658076
+ child = spawn31(npmBin, ["install", "-g", pkgSpec, "--no-audit", "--no-fund", "--no-progress"], {
657690
658077
  detached: true,
657691
658078
  stdio: ["ignore", logFd, logFd],
657692
658079
  windowsHide: true,
@@ -657708,13 +658095,13 @@ async function handleV1Update(req2, res, requestId) {
657708
658095
  }
657709
658096
  }
657710
658097
  if (npmCli) {
657711
- child = spawn32(nodeBin, [npmCli, "install", "-g", pkgSpec, "--no-audit", "--no-fund", "--no-progress"], {
658098
+ child = spawn31(nodeBin, [npmCli, "install", "-g", pkgSpec, "--no-audit", "--no-fund", "--no-progress"], {
657712
658099
  detached: true,
657713
658100
  stdio: ["ignore", logFd, logFd],
657714
658101
  env: cleanEnv
657715
658102
  });
657716
658103
  } else {
657717
- child = spawn32(npmBin, ["install", "-g", pkgSpec, "--no-audit", "--no-fund", "--no-progress"], {
658104
+ child = spawn31(npmBin, ["install", "-g", pkgSpec, "--no-audit", "--no-fund", "--no-progress"], {
657718
658105
  detached: true,
657719
658106
  stdio: ["ignore", logFd, logFd],
657720
658107
  env: cleanEnv
@@ -657725,7 +658112,7 @@ async function handleV1Update(req2, res, requestId) {
657725
658112
  const installPid = child.pid ?? 0;
657726
658113
  if (installPid > 0 && !isWin2) {
657727
658114
  try {
657728
- const follower = spawn32("bash", ["-c", `while kill -0 ${installPid} 2>/dev/null; do sleep 1; done; echo "__EXIT_CODE=0" >> "${logPath3}"`], {
658115
+ const follower = spawn31("bash", ["-c", `while kill -0 ${installPid} 2>/dev/null; do sleep 1; done; echo "__EXIT_CODE=0" >> "${logPath3}"`], {
657729
658116
  detached: true,
657730
658117
  stdio: "ignore"
657731
658118
  });
@@ -657748,7 +658135,7 @@ async function handleV1Update(req2, res, requestId) {
657748
658135
  hasSystemdUnit ? `systemctl --user restart omnius-daemon.service >/dev/null 2>&1 || true` : `${JSON.stringify(omniusAbs)} serve --quiet --daemon >/dev/null 2>&1 & disown`,
657749
658136
  `kill -TERM ${process.pid} >/dev/null 2>&1 || true`
657750
658137
  ].join("; ");
657751
- const relauncher = spawn32("bash", ["-c", relaunchScript], {
658138
+ const relauncher = spawn31("bash", ["-c", relaunchScript], {
657752
658139
  detached: true,
657753
658140
  stdio: "ignore",
657754
658141
  env: cleanEnv
@@ -657970,7 +658357,7 @@ async function handleV1Run(req2, res) {
657970
658357
  });
657971
658358
  job.sandbox = "container";
657972
658359
  } else {
657973
- child = spawn32(process.execPath, [omniusBin, ...args], {
658360
+ child = spawn31(process.execPath, [omniusBin, ...args], {
657974
658361
  cwd: cwd4,
657975
658362
  env: runEnv,
657976
658363
  stdio: ["ignore", "pipe", "pipe"],
@@ -660109,7 +660496,7 @@ ${historyLines}
660109
660496
  }
660110
660497
  runEnv["OLLAMA_HOST"] = currentCfg.backendUrl || process.env["OLLAMA_HOST"] || "http://127.0.0.1:11434";
660111
660498
  if (currentCfg.apiKey) runEnv["OMNIUS_API_KEY_INHERIT"] = currentCfg.apiKey;
660112
- const child = spawn32(process.execPath, [omniusBin, ...args], {
660499
+ const child = spawn31(process.execPath, [omniusBin, ...args], {
660113
660500
  cwd: cwdPath,
660114
660501
  env: runEnv,
660115
660502
  stdio: ["ignore", "pipe", "pipe"],
@@ -663345,7 +663732,7 @@ function buildTools(repoRoot, config, contextWindowSize, modelTier) {
663345
663732
  new CreateToolTool(repoRoot),
663346
663733
  new ManageToolsTool(repoRoot),
663347
663734
  // Load agent-created custom tools from .omnius/tools/ and ~/.omnius/tools/
663348
- ...buildCustomTools(repoRoot),
663735
+ ...buildCustomTools(repoRoot, { shellRunner: shellTool }),
663349
663736
  // Skill system (AIWG skills — discovery and execution)
663350
663737
  new SkillListTool(repoRoot),
663351
663738
  new SkillExecuteTool(repoRoot),
@@ -671876,7 +672263,7 @@ __export(run_exports, {
671876
672263
  statusCommand: () => statusCommand
671877
672264
  });
671878
672265
  import { resolve as resolve54 } from "node:path";
671879
- import { spawn as spawn33 } from "node:child_process";
672266
+ import { spawn as spawn32 } from "node:child_process";
671880
672267
  import { mkdirSync as mkdirSync83, writeFileSync as writeFileSync75, readFileSync as readFileSync112, readdirSync as readdirSync50, existsSync as existsSync137 } from "node:fs";
671881
672268
  import { randomBytes as randomBytes28 } from "node:crypto";
671882
672269
  import { join as join152 } from "node:path";
@@ -671989,7 +672376,7 @@ async function runBackground(task, config, opts) {
671989
672376
  const omniusBin = process.argv[1] || "omnius";
671990
672377
  const args = [task, "--json"];
671991
672378
  if (config.model) args.push("--model", config.model);
671992
- const child = spawn33(process.execPath, [omniusBin, ...args], {
672379
+ const child = spawn32(process.execPath, [omniusBin, ...args], {
671993
672380
  cwd: repoRoot,
671994
672381
  env: { ...process.env, OMNIUS_JOB_ID: id },
671995
672382
  stdio: ["ignore", "pipe", "pipe"],
@@ -673189,9 +673576,9 @@ async function main() {
673189
673576
  const mode = process.argv[process.argv.indexOf("--self-test") + 1] || "crossmodal";
673190
673577
  if (mode === "crossmodal") {
673191
673578
  process.stdout.write("Running crossmodal smoke tests...\n");
673192
- const { spawn: spawn34 } = await import("node:child_process");
673579
+ const { spawn: spawn33 } = await import("node:child_process");
673193
673580
  const run2 = (file) => new Promise((resolve57, reject) => {
673194
- const p2 = spawn34(process.execPath, [file], { stdio: ["ignore", "pipe", "pipe"] });
673581
+ const p2 = spawn33(process.execPath, [file], { stdio: ["ignore", "pipe", "pipe"] });
673195
673582
  p2.stdout.on("data", (d2) => process.stdout.write(d2));
673196
673583
  p2.stderr.on("data", (d2) => process.stdout.write(d2));
673197
673584
  onChildExit(p2, (code8) => code8 === 0 ? resolve57() : reject(new Error(`${file} exited ${code8}`)));