omnius 1.0.193 → 1.0.195

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/README.md CHANGED
@@ -158,8 +158,8 @@ pnpm -r build
158
158
  node scripts/build-publish.mjs
159
159
  cd publish
160
160
  mkdir -p .npm-cache
161
- NPM_CONFIG_CACHE=$(pwd)/.npm-cache npm pack
162
- NPM_CONFIG_CACHE=$(pwd)/.npm-cache npm publish --access public
161
+ NPM_CONFIG_CACHE=$(pwd)/.npm-cache npm pack --prefer-online --cache-min=0 --registry https://registry.npmjs.org/
162
+ NPM_CONFIG_CACHE=$(pwd)/.npm-cache npm publish --access public --prefer-online --cache-min=0 --registry https://registry.npmjs.org/
163
163
  ```
164
164
 
165
165
  Before publishing, verify `README.md`, `package.json`, `dist/index.js`, and `dist/launcher.cjs` are in the tarball, and that `package.json` includes `readmeFilename: "README.md"` plus a string `readme`.
package/dist/index.js CHANGED
@@ -23930,7 +23930,25 @@ function listMediaModelCatalog(modality) {
23930
23930
  function resolveMediaModel(id, modality) {
23931
23931
  const wanted = normalizeModality(modality);
23932
23932
  const normalized = normalizeRepoId(id);
23933
- return listMediaModelCatalog(wanted).find((entry) => entry.spec.id === normalized || entry.spec.repoId === normalized || mediaModelSlug(entry.spec.id) === mediaModelSlug(normalized));
23933
+ const normalizedLower = normalized.toLowerCase();
23934
+ const normalizedSlug = mediaModelSlug(normalized).toLowerCase();
23935
+ return listMediaModelCatalog(wanted).find((entry) => {
23936
+ const spec = entry.spec;
23937
+ const keys = [
23938
+ spec.id,
23939
+ spec.repoId,
23940
+ basename4(spec.repoId),
23941
+ spec.label,
23942
+ mediaModelSlug(spec.id),
23943
+ mediaModelSlug(spec.repoId),
23944
+ mediaModelSlug(basename4(spec.repoId)),
23945
+ mediaModelSlug(spec.label)
23946
+ ];
23947
+ return keys.some((key) => {
23948
+ const candidate = String(key ?? "").trim();
23949
+ return candidate.toLowerCase() === normalizedLower || mediaModelSlug(candidate).toLowerCase() === normalizedSlug;
23950
+ });
23951
+ });
23934
23952
  }
23935
23953
  function listRuntimeMediaModelSpecs(modality) {
23936
23954
  return listMediaModelCatalog(modality).map((entry) => entry.spec).filter((spec) => (spec.status === "active" || spec.status === "metadata-only") && mediaBackendCompatibleWithModality(spec.backend, modality));
@@ -267033,7 +267051,9 @@ ${llmAnnotation}` : result.llmContent;
267033
267051
  import { mkdir as mkdir15, stat as stat5, writeFile as writeFile20 } from "node:fs/promises";
267034
267052
  import { dirname as dirname9, extname as extname5, isAbsolute as isAbsolute2, join as join44, resolve as resolvePath } from "node:path";
267035
267053
  function builtInRuntimeFor(spec, kind) {
267036
- if (kind !== "cad" || spec.modality !== "cad")
267054
+ if (spec.modality !== "cad")
267055
+ return null;
267056
+ if (kind !== "cad" && !spec.modalities.includes("3d"))
267037
267057
  return null;
267038
267058
  const id = spec.id.toLowerCase();
267039
267059
  if (id === "campedersen/cad0" || id === "campedersen/cad0-mini")
@@ -267061,9 +267081,9 @@ function resolveModelOutputPath(cwd4, outputPath3, ext) {
267061
267081
  }
267062
267082
  return resolved;
267063
267083
  }
267064
- function formatCadSuccessOutput(args) {
267084
+ function formatCadSuccessOutput(args, warnings = []) {
267065
267085
  const prompt = args.prompt.length > 140 ? args.prompt.slice(0, 137) + "..." : args.prompt;
267066
- return [
267086
+ const lines = [
267067
267087
  `CAD generated: ${args.filepath}`,
267068
267088
  ` Model: ${args.model}`,
267069
267089
  ` Backend: ${args.backend}`,
@@ -267072,7 +267092,13 @@ function formatCadSuccessOutput(args) {
267072
267092
  ` Prompt: "${prompt}"`,
267073
267093
  " Compact IR:",
267074
267094
  ...args.compactIr.split("\n").map((line) => ` ${line}`)
267075
- ].join("\n");
267095
+ ];
267096
+ if (warnings.length > 0) {
267097
+ lines.push(" Notes:");
267098
+ for (const warning of warnings)
267099
+ lines.push(` - ${warning}`);
267100
+ }
267101
+ return lines.join("\n");
267076
267102
  }
267077
267103
  function buildCadScadArtifact(prompt, modelId) {
267078
267104
  const profile = inferCadProfile(prompt);
@@ -267104,7 +267130,7 @@ function inferCadProfile(prompt) {
267104
267130
  const lower = prompt.toLowerCase();
267105
267131
  const dims = inferDimensions(prompt);
267106
267132
  const holeCount = inferHoleCount(lower);
267107
- const family = /gear|tooth|teeth|cog/.test(lower) ? "gear" : /flange|bolt circle|bcd/.test(lower) ? "flange" : /standoff|spacer|bushing|sleeve/.test(lower) ? "standoff" : /enclosure|box|case|shell/.test(lower) ? "enclosure" : /l-bracket|bracket|angle/.test(lower) ? "bracket" : "plate";
267133
+ const family = /sphere|ball|orb|globe|planet/.test(lower) ? "sphere" : /cone|conical/.test(lower) ? "cone" : /cylinder|tube|pipe|rod/.test(lower) ? "cylinder" : /cube|boxy block|block/.test(lower) && !/enclosure|case|shell/.test(lower) ? "cube" : /gear|tooth|teeth|cog/.test(lower) ? "gear" : /flange|bolt circle|bcd/.test(lower) ? "flange" : /standoff|spacer|bushing|sleeve/.test(lower) ? "standoff" : /enclosure|box|case|shell/.test(lower) ? "enclosure" : /l-bracket|bracket|angle/.test(lower) ? "bracket" : "plate";
267108
267134
  const holeRadius = inferHoleRadius(lower);
267109
267135
  return {
267110
267136
  family,
@@ -267201,7 +267227,11 @@ function holePositions(count, width, depth) {
267201
267227
  }
267202
267228
  function compactIrForProfile(profile) {
267203
267229
  const lines = [];
267204
- if (profile.family === "standoff" || profile.family === "flange" || profile.family === "gear") {
267230
+ if (profile.family === "sphere") {
267231
+ lines.push(`S ${num(profile.width / 2)}`);
267232
+ } else if (profile.family === "cone") {
267233
+ lines.push(`CN ${num(profile.width / 2)} 0 ${num(profile.height)}`);
267234
+ } else if (profile.family === "standoff" || profile.family === "flange" || profile.family === "gear" || profile.family === "cylinder") {
267205
267235
  lines.push(`Y ${num(profile.width / 2)} ${num(profile.height)}`);
267206
267236
  } else {
267207
267237
  lines.push(`C ${num(profile.width)} ${num(profile.depth)} ${num(profile.height)}`);
@@ -267223,6 +267253,38 @@ function compactIrForProfile(profile) {
267223
267253
  return lines.join("\n");
267224
267254
  }
267225
267255
  function scadBodyForProfile(profile) {
267256
+ if (profile.family === "sphere") {
267257
+ return [
267258
+ "difference() {",
267259
+ " sphere(r = width / 2);",
267260
+ " for (p = hole_positions) translate([p[0], p[1], -width / 2 - 0.2]) cylinder(h = width + 0.4, r = hole_r);",
267261
+ "}"
267262
+ ].join("\n");
267263
+ }
267264
+ if (profile.family === "cube") {
267265
+ return [
267266
+ "difference() {",
267267
+ " translate([-width / 2, -width / 2, -width / 2]) cube([width, width, width]);",
267268
+ " for (p = hole_positions) translate([p[0], p[1], -width / 2 - 0.2]) cylinder(h = width + 0.4, r = hole_r);",
267269
+ "}"
267270
+ ].join("\n");
267271
+ }
267272
+ if (profile.family === "cylinder") {
267273
+ return [
267274
+ "difference() {",
267275
+ " cylinder(h = height, r = width / 2);",
267276
+ " for (p = hole_positions) translate([p[0], p[1], -0.2]) cylinder(h = height + 0.4, r = hole_r);",
267277
+ "}"
267278
+ ].join("\n");
267279
+ }
267280
+ if (profile.family === "cone") {
267281
+ return [
267282
+ "difference() {",
267283
+ " cylinder(h = height, r1 = width / 2, r2 = 0);",
267284
+ " for (p = hole_positions) translate([p[0], p[1], -0.2]) cylinder(h = height + 0.4, r = hole_r);",
267285
+ "}"
267286
+ ].join("\n");
267287
+ }
267226
267288
  if (profile.family === "standoff") {
267227
267289
  return [
267228
267290
  "difference() {",
@@ -267333,10 +267395,62 @@ function statusRank(status) {
267333
267395
  return 2;
267334
267396
  return 3;
267335
267397
  }
267398
+ function isGenericModelSelector(value2, kind) {
267399
+ const raw = value2.trim().toLowerCase();
267400
+ if (!raw)
267401
+ return true;
267402
+ if (raw === "auto" || raw === "default" || raw === "best" || raw === "smallest" || raw === "recommended")
267403
+ return true;
267404
+ if (kind === "3d") {
267405
+ return /^(3d|3d model|3d-model|model|mesh|asset|object|world|glb|obj|stl|ply)$/.test(raw);
267406
+ }
267407
+ return /^(cad|cad model|text-to-cad|parametric cad|scad|step|openscad)$/.test(raw);
267408
+ }
267409
+ function looksLikeExplicitModelAdapter(value2) {
267410
+ const raw = value2.trim();
267411
+ if (!raw)
267412
+ return false;
267413
+ if (/^https?:\/\/huggingface\.co\//i.test(raw))
267414
+ return true;
267415
+ if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\/.*)?$/.test(raw))
267416
+ return true;
267417
+ if (/^(hf|huggingface):/i.test(raw))
267418
+ return true;
267419
+ return false;
267420
+ }
267421
+ function shouldUseTextCadFallbackFor3d(kind, prompt, input, args) {
267422
+ if (kind !== "3d")
267423
+ return false;
267424
+ if (!prompt || input)
267425
+ return false;
267426
+ const outputFormat = String(args["output_format"] ?? "").trim().toLowerCase().replace(/^\./, "");
267427
+ if (outputFormat && outputFormat !== "scad" && outputFormat !== "step")
267428
+ return false;
267429
+ const outputPath3 = String(args["output_path"] ?? "").trim().toLowerCase();
267430
+ if (/\.(glb|obj|stl|ply|png|json)$/.test(outputPath3))
267431
+ return false;
267432
+ return true;
267433
+ }
267434
+ function renderSelectionError(kind, selection) {
267435
+ const label = adapterLabel(kind);
267436
+ const available = selection.available.slice(0, 8).map((entry) => entry.spec.id);
267437
+ if (selection.missingRequested) {
267438
+ return [
267439
+ `Requested ${label} "${selection.missingRequested}" was not found in the /models catalog.`,
267440
+ available.length > 0 ? `Available ${label}s include: ${available.join(", ")}.` : `No ${label}s are currently available.`,
267441
+ `Call generate_model with action='list_models' and kind='${kind}' to inspect selectable adapters.`
267442
+ ].join("\n");
267443
+ }
267444
+ return [
267445
+ `No ${label} is available in the /models catalog.`,
267446
+ "Built-in CAD/3D adapters may be disabled by OMNIUS_MEDIA_MODEL_DISABLE_BUILTINS=1.",
267447
+ "Use hf_model_discover or hf_model_intake to add a Hugging Face media model adapter."
267448
+ ].join("\n");
267449
+ }
267336
267450
  function renderCatalogForKind(kind, entries) {
267337
267451
  if (entries.length === 0)
267338
- return `No ${kindLabel(kind)} model adapters are available.`;
267339
- const lines = [`Available ${kindLabel(kind)} model adapters:`];
267452
+ return `No ${adapterLabel(kind)}s are available.`;
267453
+ const lines = [`Available ${adapterLabel(kind)}s:`];
267340
267454
  for (const entry of entries) {
267341
267455
  const spec = entry.spec;
267342
267456
  const resources = resourceSummary(spec);
@@ -267474,6 +267588,9 @@ function resourceSummary(spec) {
267474
267588
  function kindLabel(kind) {
267475
267589
  return kind === "cad" ? "CAD" : "3D model";
267476
267590
  }
267591
+ function adapterLabel(kind) {
267592
+ return kind === "cad" ? "CAD model adapter" : "3D model adapter";
267593
+ }
267477
267594
  var RUNTIME_BLOCKER, ModelGenerateTool;
267478
267595
  var init_model_generate = __esm({
267479
267596
  "packages/execution/dist/tools/model-generate.js"() {
@@ -267557,18 +267674,28 @@ var init_model_generate = __esm({
267557
267674
  durationMs: performance.now() - start2
267558
267675
  };
267559
267676
  }
267560
- const selected = this.selectModel(kind, args);
267561
- if (!selected) {
267677
+ const selection = this.selectModel(kind, args);
267678
+ if (!selection.entry) {
267679
+ const error = renderSelectionError(kind, selection);
267562
267680
  return {
267563
267681
  success: false,
267564
- output: "",
267565
- error: `No ${kindLabel(kind)} model adapter is available. Use hf_model_discover or hf_model_intake to add one.`,
267682
+ output: error,
267683
+ error,
267684
+ llmContent: JSON.stringify({
267685
+ kind,
267686
+ created: false,
267687
+ reason: selection.missingRequested ? "model_adapter_not_found" : "no_model_adapters",
267688
+ requestedModel: selection.missingRequested,
267689
+ available: selection.available.map((entry) => modelSummary(entry))
267690
+ }, null, 2),
267566
267691
  durationMs: performance.now() - start2
267567
267692
  };
267568
267693
  }
267694
+ const selected = selection.entry;
267569
267695
  const spec = selected.spec;
267570
267696
  this.emit(start2, "setup", `Selected ${spec.id} (${spec.modality}/${spec.backend})`, 10);
267571
267697
  const readiness = await this.checkReadiness(spec, kind);
267698
+ readiness.warnings.unshift(...selection.warnings);
267572
267699
  const body = renderReadiness(kind, spec, readiness);
267573
267700
  if (action === "check") {
267574
267701
  return {
@@ -267590,7 +267717,7 @@ No artifact created: ${readiness.blockers[0] ?? `${kindLabel(kind)} generation i
267590
267717
  durationMs: performance.now() - start2
267591
267718
  };
267592
267719
  }
267593
- const prompt = String(args["prompt"] ?? "").trim();
267720
+ const prompt = String(args["prompt"] ?? "").trim() || selection.promptFromModelValue || "";
267594
267721
  const input = String(args["input_image"] ?? args["input"] ?? "").trim();
267595
267722
  if (!prompt && !input) {
267596
267723
  return {
@@ -267667,21 +267794,46 @@ No artifact created: provide prompt and/or input_image for ${kindLabel(kind)} ge
267667
267794
  return "3d";
267668
267795
  }
267669
267796
  selectModel(kind, args) {
267797
+ const warnings = [];
267798
+ const entries = rankCatalogEntries(kind);
267670
267799
  const requested = String(args["model"] ?? "").trim();
267671
267800
  const configured = kind === "cad" ? this.defaults.cadModel : this.defaults.model3dModel;
267672
- const model = requested || configured || "";
267673
- if (model)
267674
- return resolveMediaModel(model, kind);
267801
+ let promptFromModelValue;
267802
+ if (requested && !isGenericModelSelector(requested, kind)) {
267803
+ const resolved = resolveMediaModel(requested, kind);
267804
+ if (resolved)
267805
+ return { entry: resolved, available: entries, warnings };
267806
+ if (looksLikeExplicitModelAdapter(requested)) {
267807
+ return { available: entries, warnings, missingRequested: requested };
267808
+ }
267809
+ promptFromModelValue = requested;
267810
+ warnings.push(`Treating model="${requested}" as the requested object description, not a /models adapter id; using automatic selection.`);
267811
+ }
267812
+ if (configured && !isGenericModelSelector(configured, kind)) {
267813
+ const resolved = resolveMediaModel(configured, kind);
267814
+ if (resolved)
267815
+ return { entry: resolved, available: entries, warnings };
267816
+ warnings.push(`Configured /models ${kind} adapter "${configured}" is not available; falling back to automatic selection.`);
267817
+ }
267675
267818
  const requestedBackend = String(args["backend"] ?? "").trim();
267676
267819
  const backend = requestedBackend || (kind === "cad" ? this.defaults.cadBackend : this.defaults.model3dBackend);
267677
- const entries = rankCatalogEntries(kind);
267678
267820
  if (backend && backend !== "auto") {
267679
267821
  const wantedBackend = backend.toLowerCase();
267680
267822
  const matching = entries.find((entry) => entry.spec.backend === wantedBackend);
267681
267823
  if (matching)
267682
- return matching;
267824
+ return { entry: matching, available: entries, warnings, promptFromModelValue };
267825
+ warnings.push(`Requested ${kindLabel(kind)} backend "${backend}" is not available; falling back to automatic selection.`);
267826
+ }
267827
+ const prompt = String(args["prompt"] ?? "").trim() || promptFromModelValue || "";
267828
+ const input = String(args["input_image"] ?? args["input"] ?? "").trim();
267829
+ if (shouldUseTextCadFallbackFor3d(kind, prompt, input, args)) {
267830
+ const cadFallback = rankCatalogEntries("cad").find((entry) => builtInRuntimeFor(entry.spec, "cad") === "cad0-scad");
267831
+ if (cadFallback) {
267832
+ warnings.push("Text-only 3D generation has no wired mesh runtime yet; using the built-in CAD/SCAD adapter to create a real 3D artifact.");
267833
+ return { entry: cadFallback, available: entries, warnings, promptFromModelValue };
267834
+ }
267683
267835
  }
267684
- return entries[0];
267836
+ return { entry: entries[0], available: entries, warnings, promptFromModelValue };
267685
267837
  }
267686
267838
  async checkReadiness(spec, kind) {
267687
267839
  const blockers = [];
@@ -267789,7 +267941,7 @@ No artifact created: ${msg}`,
267789
267941
  prompt: args.prompt,
267790
267942
  sizeKB: Math.max(1, Math.round(info.size / 1024)),
267791
267943
  compactIr: artifact.compactIr
267792
- });
267944
+ }, args.readiness.warnings);
267793
267945
  return {
267794
267946
  success: true,
267795
267947
  output,
@@ -551987,6 +552139,14 @@ function normalizeProviderToolMessage(msg, model) {
551987
552139
  toolCalls: toolCalls.length > 0 ? toolCalls : void 0
551988
552140
  };
551989
552141
  }
552142
+ function isGenerationToolName(toolName) {
552143
+ return /^(generate_image|generate_audio|generate_video|generate_model|generate_tts|create_audio_file)$/.test(toolName);
552144
+ }
552145
+ function isGenerationArtifactSuccess(toolName, output) {
552146
+ if (!isGenerationToolName(toolName))
552147
+ return false;
552148
+ return /(?:Image generated|Music generated|Sound generated|Video generated|3D model generated|CAD generated|Model generated|TTS generated|Created [A-Z]+ file|Created|Saved to|Output saved to):?\s+/i.test(output);
552149
+ }
551990
552150
  function inferEpisodeModality(toolName) {
551991
552151
  if (VISUAL_TOOLS.has(toolName))
551992
552152
  return "visual";
@@ -553486,6 +553646,8 @@ ${result.output ?? ""}`;
553486
553646
  * no todo list, returns an empty array to allow task completion.
553487
553647
  */
553488
553648
  getOpenTodoItems() {
553649
+ if (this.options.disableTodoCompletionGuard)
553650
+ return [];
553489
553651
  const todos = this.readSessionTodos();
553490
553652
  if (!todos || todos.length === 0)
553491
553653
  return [];
@@ -557651,7 +557813,7 @@ ${_staleSamples.join("\n")}` : ``,
557651
557813
  const userMsg = this.pendingUserMessages.shift();
557652
557814
  await this.appendInjectedUserMessage(userMsg, messages2, turn);
557653
557815
  }
557654
- {
557816
+ if (!this.options.disableTodoPlanningNudges) {
557655
557817
  const maybeReminder = this.getTodoReminderContent(turn);
557656
557818
  if (maybeReminder) {
557657
557819
  messages2.push({ role: "user", content: maybeReminder });
@@ -557663,7 +557825,7 @@ ${_staleSamples.join("\n")}` : ``,
557663
557825
  }
557664
557826
  }
557665
557827
  const turnTier = this.options.modelTier ?? "large";
557666
- if (turn === 0 && (turnTier === "small" || turnTier === "medium")) {
557828
+ if (turn === 0 && !this.options.disableTodoPlanningNudges && (turnTier === "small" || turnTier === "medium")) {
557667
557829
  const goal = this._taskState.goal || "";
557668
557830
  const wordCount2 = goal.split(/\s+/).length;
557669
557831
  const hasMultipleActions = /\band\b.*\band\b|then.*then|also.*also/i.test(goal);
@@ -558775,6 +558937,9 @@ ${criticDecision.cachedResult.slice(0, 500)}` : `[BLOCKED — the observer confi
558775
558937
  mode: "step_repetition",
558776
558938
  rationale: `force_progress_block on ${tc.name} after ${criticDecision.hitNumber} identical calls`
558777
558939
  });
558940
+ const generationCompletionHint = isGenerationArtifactSuccess(tc.name, criticDecision.cachedResult) ? `
558941
+
558942
+ [GENERATION ALREADY COMPLETE] This exact ${tc.name} call already succeeded. Do not call it again. Use the cached artifact/path above; if delivery is needed, send it, otherwise call task_complete.` : "";
558778
558943
  const header = criticDecision.compacted ? `[RE-SERVED FROM CACHE — the original result was compacted from context. Here is the data again. Do not retry this exact call.]
558779
558944
 
558780
558945
  ` : `[SKIPPED DUPLICATE — exact ${tc.name} call not re-run. The cached result below is from the prior successful call. Do not retry this exact call.]
@@ -558789,7 +558954,7 @@ ${truncatedCache}`);
558789
558954
  tc,
558790
558955
  output: `${criticDecision.blockMessage}
558791
558956
 
558792
- ${header}${truncatedCache}`,
558957
+ ${header}${truncatedCache}${generationCompletionHint}`,
558793
558958
  success: true
558794
558959
  };
558795
558960
  }
@@ -558807,6 +558972,9 @@ ${header}${truncatedCache}`,
558807
558972
  turn,
558808
558973
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
558809
558974
  });
558975
+ const generationCompletionHint = isGenerationArtifactSuccess(tc.name, criticDecision.cachedResult) ? `
558976
+
558977
+ [GENERATION ALREADY COMPLETE] This exact ${tc.name} call already succeeded. Do not call it again. Use the cached artifact/path above; if delivery is needed, send it, otherwise call task_complete.` : "";
558810
558978
  const header = criticDecision.compacted ? `[RE-SERVED FROM CACHE — the original result was compacted from context. Here is the data again. No need to call this tool again.]
558811
558979
 
558812
558980
  ` : `[DUPLICATE CALL #${criticDecision.hitNumber} — you already called ${tc.name} with these exact arguments. The result is identical. Do NOT call this again. Use the data you already have to make progress. One more identical call will trigger a hard progress block.]
@@ -558814,7 +558982,7 @@ ${header}${truncatedCache}`,
558814
558982
  `;
558815
558983
  const truncatedCache = criticDecision.cachedResult.length > 500 ? criticDecision.cachedResult.slice(0, 500) + `
558816
558984
  ... [${criticDecision.cachedResult.length - 500} chars omitted — same as before]` : criticDecision.cachedResult;
558817
- const dedupOutput = header + truncatedCache;
558985
+ const dedupOutput = header + truncatedCache + generationCompletionHint;
558818
558986
  markSyntheticToolLog(dedupOutput);
558819
558987
  this.emit({
558820
558988
  type: "tool_result",
@@ -560008,6 +560176,9 @@ Do NOT retry ${tc.name} with similar arguments.`);
560008
560176
  } else if (result.success && tc.name !== "task_complete") {
560009
560177
  sameToolFailStreak = 0;
560010
560178
  sameToolFailName = null;
560179
+ if (isGenerationArtifactSuccess(tc.name, result.output ?? "")) {
560180
+ this.pendingUserMessages.push(`[GENERATION COMPLETE] ${tc.name} succeeded. Do not call the same generation tool again for the same request. Use the artifact/path from the tool result; if delivery is needed, send it, otherwise call task_complete.`);
560181
+ }
560011
560182
  }
560012
560183
  if (filePath && (tc.name === "file_read" || tc.name === "file_write" || tc.name === "file_edit" || tc.name === "batch_edit" || tc.name === "file_patch")) {
560013
560184
  const isModify = tc.name !== "file_read";
@@ -564537,6 +564708,8 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
564537
564708
  "batch_edit"
564538
564709
  ]);
564539
564710
  const taskText = (this._taskState.originalGoal || this._taskState.goal || "").toLowerCase();
564711
+ const wants3dModelGeneration = /\b(?:make|create|generate|build|produce|render|give)\b/.test(taskText) && /\b(?:3d|three[-\s]?d|mesh|glb|obj|stl|ply|cad|scad|step|printable|model)\b/.test(taskText) && !/\b(?:image|picture|photo|rendering|screenshot)\b/.test(taskText);
564712
+ const wantsModelCatalogManagement = /\b(?:discover|search|find|add|install|intake|inspect|validate|save|catalog|adapter|hugging\s*face|hf)\b/.test(taskText) && /\b(?:model|adapter|hugging\s*face|hf)\b/.test(taskText);
564540
564713
  const taskWords = new Set(taskText.split(/\s+/).filter((w) => w.length > 2));
564541
564714
  const scored = [];
564542
564715
  for (const tool of allTools) {
@@ -564557,6 +564730,15 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
564557
564730
  if (taskText.includes(tool.name.replace(/_/g, " ")) || taskText.includes(tool.name)) {
564558
564731
  score += customMeta ? 16 : 10;
564559
564732
  }
564733
+ if (wants3dModelGeneration) {
564734
+ if (tool.name === "generate_model")
564735
+ score += 40;
564736
+ if (tool.name === "generate_image")
564737
+ score -= 12;
564738
+ if ((tool.name === "hf_model_discover" || tool.name === "hf_model_intake") && !wantsModelCatalogManagement) {
564739
+ score -= 18;
564740
+ }
564741
+ }
564560
564742
  if (customMeta) {
564561
564743
  const lastStatus = customMeta.qualityGate?.lastTest?.status;
564562
564744
  if (lastStatus === "passed")
@@ -633935,6 +634117,9 @@ file access, and code analysis. Respond thoroughly and helpfully.
633935
634117
  When asked to send a generated or existing file to Telegram, call telegram_send_file with
633936
634118
  path and target. Do not search for Telegram bot tokens, environment secrets, or Bot API
633937
634119
  credentials; upload authorization is encapsulated by telegram_send_file.
634120
+ For admin-DM artifact generation requests, send the generated file with
634121
+ telegram_send_file after the generation tool succeeds unless the admin
634122
+ explicitly asked only for a local path.
633938
634123
 
633939
634124
  When asked to generate speech, narration, or TTS, use generate_tts or audio_playback.
633940
634125
  Those tools handle first-use backend setup where supported. Do not fall back to shell
@@ -633943,6 +634128,9 @@ commands or generic audio generation for speech synthesis while those tools are
633943
634128
  When asked to generate a 3D asset, mesh, printable model, or CAD part, use
633944
634129
  generate_model. Use hf_model_discover or hf_model_intake only when the admin is
633945
634130
  asking to add, inspect, validate, or save model adapters.
634131
+ For text-only 3D requests, pass the requested object in prompt and do not put
634132
+ the object name in model; model is only for exact /models adapter ids. Do not
634133
+ use generate_image as a substitute for a 3D/CAD artifact.
633946
634134
 
633947
634135
  Keep responses concise for Telegram but don't withhold information from the admin.
633948
634136
  `.trim();
@@ -634057,6 +634245,7 @@ Telegram response contract:
634057
634245
  - Do not summarize the fact that you answered; the visible assistant text must be the answer itself.
634058
634246
  - If you delegated long-running work, include the sub-agent id/status and what the admin should expect next.
634059
634247
  - Do not narrate retry strategy ("let me try one more approach", "actually", "wait, let me try") in assistant text. If you are stuck, send a single concise blocker sentence as the reply instead of streaming deliberation.
634248
+ - Treat todo_write as optional scratch state in Telegram runs. Do not create or maintain todos for simple Q&A, capability checks, or single artifact-generation requests.
634060
634249
  `.trim();
634061
634250
  TELEGRAM_EXTERNAL_ACQUISITION_CONTRACT = `
634062
634251
  External acquisition contract:
@@ -634243,10 +634432,6 @@ Telegram link integrity contract:
634243
634432
  TELEGRAM_ALLOWED_UPDATES = ["message", "guest_message", "callback_query", "poll", "message_reaction", "message_reaction_count"];
634244
634433
  TELEGRAM_DEFAULT_LONG_POLL_TIMEOUT_SECONDS = 50;
634245
634434
  TELEGRAM_DEFAULT_ROUTER_MODEL_CANDIDATES = [
634246
- "qwen3:0.6b",
634247
- "qwen3:1.7b",
634248
- "qwen3:4b",
634249
- "qwen3:8b",
634250
634435
  "qwen2.5:3b",
634251
634436
  "qwen2.5:7b",
634252
634437
  "llama3.2:1b",
@@ -634254,7 +634439,11 @@ Telegram link integrity contract:
634254
634439
  "gemma3:1b",
634255
634440
  "gemma3:4b",
634256
634441
  "phi3:mini",
634257
- "phi4-mini:latest"
634442
+ "phi4-mini:latest",
634443
+ "qwen3:0.6b",
634444
+ "qwen3:1.7b",
634445
+ "qwen3:4b",
634446
+ "qwen3:8b"
634258
634447
  ];
634259
634448
  TELEGRAM_PUBLIC_TOOL_QUOTAS = {
634260
634449
  web: { limit: 20, windowMs: 60 * 6e4 },
@@ -639205,6 +639394,19 @@ ${retryText}`,
639205
639394
  const candidates = raw ? raw.split(/[,\s]+/).map((part) => part.trim()).filter(Boolean) : TELEGRAM_DEFAULT_ROUTER_MODEL_CANDIDATES;
639206
639395
  return Array.from(new Set(candidates));
639207
639396
  }
639397
+ telegramRouterAllowThinkHeavyAutoModels() {
639398
+ const raw = (process.env["OMNIUS_TG_ROUTER_ALLOW_THINK_MODELS"] ?? "").trim().toLowerCase();
639399
+ return raw === "1" || raw === "true" || raw === "on";
639400
+ }
639401
+ telegramRouterModelLooksThinkHeavy(name10) {
639402
+ return /\b(?:qwen3|qwq|deepseek-r1|r1-|reasoning)\b/i.test(name10);
639403
+ }
639404
+ orderTelegramRouterCandidates(candidates) {
639405
+ if (this.telegramRouterAllowThinkHeavyAutoModels()) return candidates;
639406
+ const stable = candidates.filter((candidate) => !this.telegramRouterModelLooksThinkHeavy(candidate));
639407
+ const thinkHeavy = candidates.filter((candidate) => this.telegramRouterModelLooksThinkHeavy(candidate));
639408
+ return [...stable, ...thinkHeavy];
639409
+ }
639208
639410
  normalizeOllamaModelNameForMatch(name10) {
639209
639411
  return name10.trim().toLowerCase().replace(/:latest$/, "");
639210
639412
  }
@@ -639235,7 +639437,7 @@ ${retryText}`,
639235
639437
  source: "main"
639236
639438
  };
639237
639439
  }
639238
- const candidates = this.telegramRouterCandidateModels();
639440
+ const candidates = this.orderTelegramRouterCandidates(this.telegramRouterCandidateModels());
639239
639441
  const cacheKey = `${config.backendUrl}
639240
639442
  ${config.model}
639241
639443
  ${candidates.join(",")}`;
@@ -639263,7 +639465,7 @@ ${candidates.join(",")}`;
639263
639465
  atMs: now,
639264
639466
  model: selected,
639265
639467
  source: "auto-small",
639266
- detail: "selected first installed OMNIUS_TG_ROUTER_MODEL_CANDIDATES entry from Ollama /api/tags"
639468
+ detail: "selected first installed Telegram router candidate from Ollama /api/tags; think-heavy models are tried last unless OMNIUS_TG_ROUTER_ALLOW_THINK_MODELS=1"
639267
639469
  };
639268
639470
  this.telegramRouterModelCache = resolved;
639269
639471
  return {
@@ -640425,7 +640627,7 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
640425
640627
  return result.result?.message_id ?? null;
640426
640628
  } catch (err) {
640427
640629
  const errStr = err instanceof Error ? err.message : String(err);
640428
- if (this.shouldLogTelegramSendFailure(errStr)) {
640630
+ if (process.env["OMNIUS_TELEGRAM_DEBUG_SEND_FAILURES"] === "1" && this.shouldLogTelegramSendFailure(errStr)) {
640429
640631
  this.tuiWrite(() => renderWarning(`Failed to send Telegram live message: ${errStr}`));
640430
640632
  }
640431
640633
  this.updateTelegramTextDeliveryCapability(chatId, {
@@ -641851,6 +642053,8 @@ ${conversationStream}`
641851
642053
  compactionThreshold: this.telegramFallbackCompactionThreshold(modelTier),
641852
642054
  contextWindowSize,
641853
642055
  modelTier,
642056
+ disableTodoCompletionGuard: true,
642057
+ disableTodoPlanningNudges: true,
641854
642058
  streamEnabled: true,
641855
642059
  dynamicContext: sessionContext.context,
641856
642060
  captureContextFrame: true,
@@ -642044,7 +642248,7 @@ ${currentTelegramPrompt}`;
642044
642248
  "If the user asks you to create an image, audio file, video, 3D/CAD model, or document artifact, create it with the scoped creative tools. Freshly generated artifacts are recorded and automatically attached to this Telegram chat when the turn completes, so do not call telegram_send_file for those same artifacts unless the user asked for a specific caption, existing/unrecorded file, or non-default target.",
642045
642249
  "For image generation requests, decide from the conversation whether generate_image is appropriate; do not ask the user to use a hardcoded shortcut when the request is clear.",
642046
642250
  "For video generation requests, decide whether generate_video is appropriate. Use mode='i2v' (image-to-video) when the user references an attached or already-generated image; otherwise mode='t2v'. Warn the user up front that video generation typically takes 2-10 minutes on consumer GPUs, and that the public Telegram video-generation quota is tight (2/hour/user).",
642047
- "For 3D asset, mesh, printable model, or CAD generation requests, use generate_model. Use kind='cad' for parametric/mechanical parts and kind='3d' for mesh/asset/world requests. If the tool reports a catalog/runtime blocker, explain that blocker plainly.",
642251
+ "For 3D asset, mesh, printable model, or CAD generation requests, use generate_model. Use kind='cad' for parametric/mechanical parts and kind='3d' for mesh/asset/world requests. For text-only 3D requests, put the object description in prompt and omit model unless selecting an exact /models adapter id. Do not use generate_image as a substitute for a 3D/CAD artifact. If the tool reports a catalog/runtime blocker, explain that blocker plainly.",
642048
642252
  creativeWorkspace
642049
642253
  ].filter(Boolean).join("\n\n");
642050
642254
  userPrompt = `${systemPrompt}${discretionPrompt}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.193",
3
+ "version": "1.0.195",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.193",
9
+ "version": "1.0.195",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.193",
3
+ "version": "1.0.195",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -142,5 +142,5 @@
142
142
  "transcribe-cli": "^2.0.1",
143
143
  "viem": "2.47.4"
144
144
  },
145
- "readme": "# Omnius\n\nOmnius is a local-first agentic coding runtime: terminal UI, autonomous coding loop, REST daemon, model router, memory layer, media tools, Telegram bridge, and peer-to-peer inference mesh in one CLI.\n\nIt is designed for open-weight and user-controlled models first, while still routing cleanly through Ollama, vLLM, OpenAI-compatible endpoints, OpenRouter, Groq, Chutes, sponsor peers, COHERE peers, and other configured providers.\n\n[![npm](https://img.shields.io/npm/v/omnius.svg)](https://www.npmjs.com/package/omnius)\n[![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](https://nodejs.org/)\n[![License](https://img.shields.io/badge/license-CC--BY--NC--4.0-blue.svg)](LICENSE)\n\n## Install\n\n```bash\nnpm install -g omnius\nomnius\n```\n\nRequirements:\n\n- Node.js 22 or newer\n- npm 10 or newer for published CLI use\n- pnpm 9 or newer for workspace development\n- A local model or configured remote endpoint\n\nStart the REST daemon:\n\n```bash\nomnius serve\n```\n\nThe daemon defaults to `http://127.0.0.1:11435`. Open the interactive API docs at `http://127.0.0.1:11435/docs`.\n\n## What Omnius Does\n\n- Runs autonomous coding tasks, edits files, executes tools, tests changes, and iterates on failures.\n- Provides a dense terminal UI for model selection, endpoint routing, task control, shell output, voice, sponsors, Telegram, and system telemetry.\n- Exposes a REST daemon with OpenAI/Ollama-compatible inference, agentic task execution, memory, skills, tools, MCP, events, voice, projects, and governance endpoints.\n- Routes models through local, cloud, sponsor, and peer-to-peer endpoints without assuming local Ollama is the only source.\n- Supports realtime spoken conversation for ASR/TTS clients through `/realtime` and REST `realtime: true`.\n- Supports image, video, sound, music, TTS, ASR, voice clone references, Telegram media workflows, and sponsor-provided media generation.\n- Keeps project runtime state in `.omnius/`, which is intentionally ignored by git.\n\n## Common Workflows\n\n```bash\nomnius \"inspect this repo and summarize the main entrypoints\"\nomnius serve\n```\n\n```text\n/help command help\n/model select or inspect the active model\n/endpoint select or configure local, cloud, sponsor, or peer endpoints\n/realtime toggle short ASR/TTS-oriented conversation mode\n/broker inspect model broker, RAM/VRAM thresholds, and loaded models\n/sponsor expose local or upstream capacity to peers\n/cohere participate in distributed COHERE inference\n/telegram configure or toggle the Telegram bridge\n/skills list explorable skills and docs memories\n/pause pause after the current turn boundary\n/stop interrupt the active run\n/resume resume saved state\n```\n\n## Current Feature Areas\n\n| Area | What to read |\n| --- | --- |\n| Install and setup | [Install](docs/getting-started/install.md), [First run](docs/getting-started/first-run.md), [Model providers](docs/getting-started/model-providers.md) |\n| Terminal workflows | [TUI workflows](docs/guides/tui-workflows.md), [Slash commands](docs/reference/slash-commands.md) |\n| REST daemon | [REST reference](docs/reference/rest-api.md), [REST quickref](docs/rest/QUICKREF.md), [OpenAPI source](docs/rest/openapi-source.md) |\n| Realtime voice chat | [Realtime guide](docs/guides/realtime.md) |\n| Sponsor and COHERE mesh | [Sponsor and COHERE guide](docs/guides/sponsor-and-cohere.md) |\n| Telegram bridge | [Telegram guide](docs/guides/telegram.md) |\n| Media generation | [Media guide](docs/guides/media-generation.md) |\n| Operations | [Runtime hygiene](docs/operations/runtime-hygiene.md), [Security and remote access](docs/operations/security-and-remote-access.md) |\n| Architecture | [Architecture overview](docs/architecture/overview.md) |\n| Agent-explorable docs | [Agent memory docs index](docs/agent-memory/INDEX.md) |\n\n## Recent Highlights\n\n- `/realtime` and REST `realtime: true` provide short, natural, SOUL.md-aware conversation for ASR/TTS clients.\n- Endpoint setup and sponsor setup aggregate models from all enabled endpoints, including external OpenAI-compatible routers.\n- `/sponsor` can expose text inference and media generation for image, video, sound, and music with per-modality limits.\n- Sponsor and COHERE status surfaces now use shared telemetry concepts: concurrency, request rate, daily tokens, peer usage, model usage, and remote system metrics.\n- The TUI reports token production rate as `t/s`, supports Shift+Enter multiline input, and renders dynamic shell output inside bounded Unicode cards.\n- Telegram state is scoped by user and group, supports durable reply preferences, and feeds raw platform/tool failures back into the agent loop.\n- Ollama pool cleanup now accounts for process groups and orphan runner processes that can keep VRAM pinned.\n- REST documentation is available both as human docs and as Omnius-discoverable docs skills.\n\n## REST API\n\nStart:\n\n```bash\nomnius serve\n```\n\nUseful entrypoints:\n\n```text\nGET /docs\nGET /openapi.json\nPOST /v1/chat\nPOST /v1/chat/completions\nPOST /v1/run\nGET /v1/events\nGET /v1/skills\nPOST /v1/tools/{name}/call\nWS /v1/voicechat/ws\n```\n\nFor shared deployments, use bearer keys:\n\n```bash\nOMNIUS_API_KEYS=\"read-key:read:grafana,run-key:run:ci:60:100000:3,admin-key:admin:ops\" omnius serve\n```\n\nSee [docs/reference/rest-api.md](docs/reference/rest-api.md) for the maintained endpoint inventory. The canonical machine contract is generated from [packages/cli/src/api/openapi.ts](packages/cli/src/api/openapi.ts).\n\n## Agent-Explorable Documentation\n\nOmnius discovers project-local docs skills from `.aiwg/addons/*/skills`. The docs bundles in this repo expose high-signal entrypoints for agents:\n\n```text\n/skills omnius docs\nskill_execute name=\"omnius-docs\"\nskill_execute name=\"omnius-rest-docs\"\nskill_extract name=\"omnius-realtime-docs\" query=\"How does realtime REST mode work?\"\n```\n\nThe intended pattern is index first, targeted document second, not loading the whole manual into the active context.\n\n## Development\n\n```bash\npnpm install\npnpm -r build\npnpm docs:check\n```\n\nFocused checks used for the docs skill surface:\n\n```bash\npnpm --filter @omnius/execution exec vitest run tests/skill-discovery.test.ts\npnpm --filter omnius exec vitest run tests/realtime-mode.test.ts tests/command-registry.test.ts\n```\n\n## Publishing\n\nPublish only from `publish/`.\n\n```bash\ncd omnius\npnpm -r clean || true\nfind . -name 'tsconfig.tsbuildinfo' -not -path '*/node_modules/*' -delete\npnpm -r build\nnode scripts/build-publish.mjs\ncd publish\nmkdir -p .npm-cache\nNPM_CONFIG_CACHE=$(pwd)/.npm-cache npm pack\nNPM_CONFIG_CACHE=$(pwd)/.npm-cache npm publish --access public\n```\n\nBefore publishing, verify `README.md`, `package.json`, `dist/index.js`, and `dist/launcher.cjs` are in the tarball, and that `package.json` includes `readmeFilename: \"README.md\"` plus a string `readme`.\n\n## License\n\nOmnius is released under [CC-BY-NC-4.0](LICENSE) for non-commercial use. Commercial use, redistribution, hosted services, and enterprise deployment require a commercial license.\n"
145
+ "readme": "# Omnius\n\nOmnius is a local-first agentic coding runtime: terminal UI, autonomous coding loop, REST daemon, model router, memory layer, media tools, Telegram bridge, and peer-to-peer inference mesh in one CLI.\n\nIt is designed for open-weight and user-controlled models first, while still routing cleanly through Ollama, vLLM, OpenAI-compatible endpoints, OpenRouter, Groq, Chutes, sponsor peers, COHERE peers, and other configured providers.\n\n[![npm](https://img.shields.io/npm/v/omnius.svg)](https://www.npmjs.com/package/omnius)\n[![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](https://nodejs.org/)\n[![License](https://img.shields.io/badge/license-CC--BY--NC--4.0-blue.svg)](LICENSE)\n\n## Install\n\n```bash\nnpm install -g omnius\nomnius\n```\n\nRequirements:\n\n- Node.js 22 or newer\n- npm 10 or newer for published CLI use\n- pnpm 9 or newer for workspace development\n- A local model or configured remote endpoint\n\nStart the REST daemon:\n\n```bash\nomnius serve\n```\n\nThe daemon defaults to `http://127.0.0.1:11435`. Open the interactive API docs at `http://127.0.0.1:11435/docs`.\n\n## What Omnius Does\n\n- Runs autonomous coding tasks, edits files, executes tools, tests changes, and iterates on failures.\n- Provides a dense terminal UI for model selection, endpoint routing, task control, shell output, voice, sponsors, Telegram, and system telemetry.\n- Exposes a REST daemon with OpenAI/Ollama-compatible inference, agentic task execution, memory, skills, tools, MCP, events, voice, projects, and governance endpoints.\n- Routes models through local, cloud, sponsor, and peer-to-peer endpoints without assuming local Ollama is the only source.\n- Supports realtime spoken conversation for ASR/TTS clients through `/realtime` and REST `realtime: true`.\n- Supports image, video, sound, music, TTS, ASR, voice clone references, Telegram media workflows, and sponsor-provided media generation.\n- Keeps project runtime state in `.omnius/`, which is intentionally ignored by git.\n\n## Common Workflows\n\n```bash\nomnius \"inspect this repo and summarize the main entrypoints\"\nomnius serve\n```\n\n```text\n/help command help\n/model select or inspect the active model\n/endpoint select or configure local, cloud, sponsor, or peer endpoints\n/realtime toggle short ASR/TTS-oriented conversation mode\n/broker inspect model broker, RAM/VRAM thresholds, and loaded models\n/sponsor expose local or upstream capacity to peers\n/cohere participate in distributed COHERE inference\n/telegram configure or toggle the Telegram bridge\n/skills list explorable skills and docs memories\n/pause pause after the current turn boundary\n/stop interrupt the active run\n/resume resume saved state\n```\n\n## Current Feature Areas\n\n| Area | What to read |\n| --- | --- |\n| Install and setup | [Install](docs/getting-started/install.md), [First run](docs/getting-started/first-run.md), [Model providers](docs/getting-started/model-providers.md) |\n| Terminal workflows | [TUI workflows](docs/guides/tui-workflows.md), [Slash commands](docs/reference/slash-commands.md) |\n| REST daemon | [REST reference](docs/reference/rest-api.md), [REST quickref](docs/rest/QUICKREF.md), [OpenAPI source](docs/rest/openapi-source.md) |\n| Realtime voice chat | [Realtime guide](docs/guides/realtime.md) |\n| Sponsor and COHERE mesh | [Sponsor and COHERE guide](docs/guides/sponsor-and-cohere.md) |\n| Telegram bridge | [Telegram guide](docs/guides/telegram.md) |\n| Media generation | [Media guide](docs/guides/media-generation.md) |\n| Operations | [Runtime hygiene](docs/operations/runtime-hygiene.md), [Security and remote access](docs/operations/security-and-remote-access.md) |\n| Architecture | [Architecture overview](docs/architecture/overview.md) |\n| Agent-explorable docs | [Agent memory docs index](docs/agent-memory/INDEX.md) |\n\n## Recent Highlights\n\n- `/realtime` and REST `realtime: true` provide short, natural, SOUL.md-aware conversation for ASR/TTS clients.\n- Endpoint setup and sponsor setup aggregate models from all enabled endpoints, including external OpenAI-compatible routers.\n- `/sponsor` can expose text inference and media generation for image, video, sound, and music with per-modality limits.\n- Sponsor and COHERE status surfaces now use shared telemetry concepts: concurrency, request rate, daily tokens, peer usage, model usage, and remote system metrics.\n- The TUI reports token production rate as `t/s`, supports Shift+Enter multiline input, and renders dynamic shell output inside bounded Unicode cards.\n- Telegram state is scoped by user and group, supports durable reply preferences, and feeds raw platform/tool failures back into the agent loop.\n- Ollama pool cleanup now accounts for process groups and orphan runner processes that can keep VRAM pinned.\n- REST documentation is available both as human docs and as Omnius-discoverable docs skills.\n\n## REST API\n\nStart:\n\n```bash\nomnius serve\n```\n\nUseful entrypoints:\n\n```text\nGET /docs\nGET /openapi.json\nPOST /v1/chat\nPOST /v1/chat/completions\nPOST /v1/run\nGET /v1/events\nGET /v1/skills\nPOST /v1/tools/{name}/call\nWS /v1/voicechat/ws\n```\n\nFor shared deployments, use bearer keys:\n\n```bash\nOMNIUS_API_KEYS=\"read-key:read:grafana,run-key:run:ci:60:100000:3,admin-key:admin:ops\" omnius serve\n```\n\nSee [docs/reference/rest-api.md](docs/reference/rest-api.md) for the maintained endpoint inventory. The canonical machine contract is generated from [packages/cli/src/api/openapi.ts](packages/cli/src/api/openapi.ts).\n\n## Agent-Explorable Documentation\n\nOmnius discovers project-local docs skills from `.aiwg/addons/*/skills`. The docs bundles in this repo expose high-signal entrypoints for agents:\n\n```text\n/skills omnius docs\nskill_execute name=\"omnius-docs\"\nskill_execute name=\"omnius-rest-docs\"\nskill_extract name=\"omnius-realtime-docs\" query=\"How does realtime REST mode work?\"\n```\n\nThe intended pattern is index first, targeted document second, not loading the whole manual into the active context.\n\n## Development\n\n```bash\npnpm install\npnpm -r build\npnpm docs:check\n```\n\nFocused checks used for the docs skill surface:\n\n```bash\npnpm --filter @omnius/execution exec vitest run tests/skill-discovery.test.ts\npnpm --filter omnius exec vitest run tests/realtime-mode.test.ts tests/command-registry.test.ts\n```\n\n## Publishing\n\nPublish only from `publish/`.\n\n```bash\ncd omnius\npnpm -r clean || true\nfind . -name 'tsconfig.tsbuildinfo' -not -path '*/node_modules/*' -delete\npnpm -r build\nnode scripts/build-publish.mjs\ncd publish\nmkdir -p .npm-cache\nNPM_CONFIG_CACHE=$(pwd)/.npm-cache npm pack --prefer-online --cache-min=0 --registry https://registry.npmjs.org/\nNPM_CONFIG_CACHE=$(pwd)/.npm-cache npm publish --access public --prefer-online --cache-min=0 --registry https://registry.npmjs.org/\n```\n\nBefore publishing, verify `README.md`, `package.json`, `dist/index.js`, and `dist/launcher.cjs` are in the tarball, and that `package.json` includes `readmeFilename: \"README.md\"` plus a string `readme`.\n\n## License\n\nOmnius is released under [CC-BY-NC-4.0](LICENSE) for non-commercial use. Commercial use, redistribution, hosted services, and enterprise deployment require a commercial license.\n"
146
146
  }