summaryrefslogtreecommitdiff
path: root/modules/core/core-bundle.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/core-bundle.js')
-rw-r--r--modules/core/core-bundle.js78
1 files changed, 70 insertions, 8 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index 69b5e1a..b365fa0 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -4080,14 +4080,15 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
// bump LATEST_VER + main.html ?v=, re-run admin/scripts/probe-ui-modes.py.
// Tracked in .hermes/plans/2026-11-09_*-newos4-ui-rollback.md
// this._safeRegister('UIShellModule', 'UI shell');
- // v4.5.47: Critical ext modules promoted to boot path
- this._safeRegister('EditorModule', 'editor');
- this._safeRegister('ShellModule', 'shell');
- this._safeRegister('AliasModule', 'aliases');
- this._safeRegister('FinderModule', 'finder');
- this._safeRegister('FileManagerModule', 'file manager');
- this._safeRegister('DiffModule', 'diff');
- this._safeRegister('BusyBoxModule', 'busybox');
+ // v4.5.47 used to _safeRegister() seven critical ext classes here (EditorModule,
+ // ShellModule, AliasModule, FinderModule, FileManagerModule, DiffModule,
+ // BusyBoxModule). They are declared in modules/ext/critical-ext-bundle.js, which is
+ // a <script> AFTER module-loader.js — i.e. it does not exist yet when
+ // `new WebX11Terminal()` runs, so every one of those calls was a guaranteed no-op
+ // that only emitted "[SafeRegister] Skipped … is not in the registry map" on each
+ // page load (adding them to the registry map would still resolve to undefined).
+ // critical-ext-bundle.js owns them and registers each one itself on load.
+ // Do NOT re-add them here — that just restores the warnings.
}
_initRegisteredModules() {
for (const [name, mod] of Object.entries(this.moduleManager.modules)) {
@@ -4127,6 +4128,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
DiffModule: typeof DiffModule !== 'undefined' ? DiffModule : undefined,
BusyBoxModule: typeof BusyBoxModule !== 'undefined' ? BusyBoxModule : undefined,
SmartContextModule: typeof SmartContextModule !== 'undefined' ? SmartContextModule : undefined,
+ SimpleGUIModule: typeof SimpleGUIModule !== 'undefined' ? SimpleGUIModule : undefined,
DebugSupervisorModule: typeof DebugSupervisorModule !== 'undefined' ? DebugSupervisorModule : undefined,
UIShellModule: typeof UIShellModule !== 'undefined' ? UIShellModule : undefined,
BillingModule: typeof BillingModule !== 'undefined' ? BillingModule : undefined,
@@ -10518,6 +10520,54 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
_pathArg(v) {
return typeof v === 'string' ? v : (v == null ? '' : String(v));
}
+ // Single boundary coercion for tool arguments. The schemas already say
+ // which args are strings, but models send numbers/arrays/objects anyway
+ // and a handler's `a.field || ''` only catches FALSY values — so
+ // `.trim()`/`.split()`/`.replace()` on a truthy number or object threw a
+ // TypeError and the call was lost (one filed auto-report per tool).
+ // Coerce once here, per declared schema type, instead of patching ~26
+ // handlers. Never throws: an unknown tool, bad defs or a non-object
+ // argument all fall back to the original value.
+ _coerceToolArgs(name, a) {
+ try {
+ if (!a || typeof a !== 'object' || Array.isArray(a)) return a;
+ var defs = this._toolDefs;
+ if (!defs) { defs = this._buildToolDefs(); this._toolDefs = defs; }
+ if (!Array.isArray(defs)) return a;
+ var props = null;
+ for (var i = 0; i < defs.length; i++) {
+ var fn = defs[i] && defs[i].function;
+ if (fn && fn.name === name) {
+ props = (fn.parameters && fn.parameters.properties) || null;
+ break;
+ }
+ }
+ if (!props) return a;
+ for (var p in props) {
+ if (!Object.prototype.hasOwnProperty.call(a, p)) continue;
+ var v = a[p];
+ // Absent/null stays absent: _pathArg and the handlers' own
+ // defaults still own "no value given".
+ if (v === null || v === undefined) continue;
+ var type = props[p] && props[p].type;
+ if (type === 'string') {
+ if (typeof v === 'string') continue;
+ if (typeof v === 'number' || typeof v === 'boolean') {
+ a[p] = String(v);
+ } else if (typeof v === 'object') {
+ // Structured content must reach the file as readable
+ // JSON, never "[object Object]".
+ try { a[p] = JSON.stringify(v); } catch (_e) { a[p] = String(v); }
+ }
+ } else if (type === 'integer' || type === 'number') {
+ // Only convert unambiguous numeric strings; anything else
+ // is left for the handler to validate/report.
+ if (typeof v === 'string' && /^-?\d+(\.\d+)?$/.test(v)) a[p] = Number(v);
+ }
+ }
+ return a;
+ } catch (_e) { return a; }
+ }
_updateDebugPanel() {
if (!this._debugPanelEnabled) return;
const panel = document.getElementById('debug-ai-panel');
@@ -11538,6 +11588,13 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
this.terminal.print(`🟡 GUARDRAIL WARNING: ${guardrail.description} (rule: ${guardrail.id})`, 'warning');
}
a = typeof argsStr === 'object' ? argsStr : (argsStr ? JSON.parse(argsStr) : {});
+ // A body of "null" / "[1,2]" parses to a non-object, and every
+ // handler below reads a.field — normalise that once here.
+ if (!a || typeof a !== 'object' || Array.isArray(a)) a = {};
+ // ONE schema-driven coercion for the whole switch: a model that
+ // sends a number/array/object for a schema-string field must not
+ // reach `.trim()`/`.split()` still holding that type.
+ a = this._coerceToolArgs(name, a);
switch (name) {
case 'list_files': {
const originalPath = this._pathArg(a.path);
@@ -12448,6 +12505,11 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
let a = {};
try {
a = typeof argsStr === 'object' ? argsStr : (argsStr ? JSON.parse(argsStr) : {});
+ if (!a || typeof a !== 'object' || Array.isArray(a)) a = {};
+ // Same single boundary as runTool: module tools take a string
+ // `name`/`code`/`file` too and are reachable directly and via
+ // runTool's `case 'view_module':` delegation.
+ a = this._coerceToolArgs(name, a);
switch (name) {
case 'view_module': {
const modName = a.name || '';