aboutsummaryrefslogtreecommitdiff
path: root/modules/core
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-29 12:29:14 +0100
committerAgando <roduccomm@gmail.com>2026-10-29 12:29:14 +0100
commit12968861b81c6aef3ca15488476db0b33fbf8104 (patch)
tree69809c163273389ef643c73695e7368f9ce3ed3e /modules/core
parent5178cea00a841b5fa57db2235b5163e271c8a762 (diff)
fix(newos4): js keeps its string literals — dispatcher forwards the raw command line (v4.6.130)
_parseArgs() treats quote characters as delimiters and drops them, so every string literal in a js expression died before eval: `js "abc".toUpperCase()` reached the module as `abc.toUpperCase()` → ReferenceError: abc is not defined. Same for `js import('fs')` → `import(fs)`. Found while verifying the v4.6.128 console fix (plan 2026-10-29_112000-newos4-parseargs-strips-js-string-literals.md). - terminal.js: checkAndRun() records the untokenised line (`_currentRaw`), _dispatch() forwards it, dispatchCommand(cmd, args, rawText) passes it to the module. Security classification still runs on the tokenised form, and pipes/AI/script-runner calls pass nothing, so they behave as before. - console.js: `js` evaluates _expr(args, rawText) — the raw remainder. `node -e` deliberately does NOT: the tokenizer already keeps `'…'` inside `"…"`, and the raw form would hand eval the string literal `"import('fs')"` instead of the import (caught by the live probe, not by review). js-run keeps tokenised paths. - modules/test/test-parse-args.js: tokenizer table (records the deletion as the current contract), dispatcher plumbing asserts, _expr cases, and the user-visible paths. 27 assertions. - module-loader.js + main.html + bundle: v4.6.130 (129 was minted, then console.js changed again — re-minting so no cached client keeps the intermediate). Verification node modules/test/test-parse-args.js -> 27/27 pass same file against HEAD sources (git show HEAD:...) -> TypeError: _expr is not a function (RED) scripts/lint-no-undef.py terminal.js console.js -> clean, 122 cross-file globals core-bundle.js rebuilt from HEAD sources + this patch in /tmp/iso (the working tree carries another session's uncommitted ollama-manager.js `_httpError`); diff vs HEAD bundle = 14 lines, all mine. critical-ext-bundle.js byte-identical. Live page v4.6.130 (real executeCommand, output captured): js "abc".toUpperCase() -> ABC js 'a b'.split(' ').length -> 2 js JSON.stringify({a:1}) -> {"a":1} js 6*7 -> 42 js import('fs') -> red "Failed to resolve module specifier 'fs'" + Node-builtin hint node -e "import('fs')" -> same error + hint (was printing the expression as a string) echo "two words" -> two words js-run /nope.js -> js-run: /nope.js: No such file unhandledrejection count -> 0 The sibling's parked terminal.js hunk (BillingModule registry entry) was stashed for this commit and is restored in the working tree afterwards; it is not part of this change.
Diffstat (limited to 'modules/core')
-rw-r--r--modules/core/core-bundle.js14
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/terminal.js14
3 files changed, 23 insertions, 7 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index fcf1505..9682596 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -2788,6 +2788,11 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
return null;
}
async checkAndRun(rawInput) {
+ // v4.6.130: keep the untokenised line for the command itself. _parseArgs turns
+ // quote characters into delimiters and drops them, so `js "abc".toUpperCase()`
+ // reaches the module as `abc.toUpperCase()` and every string literal dies.
+ // Security classification still runs on the tokenised form below.
+ this._currentRaw = rawInput;
const badOp = this.detectComposition(rawInput);
if (badOp) {
this.terminal.print('⚠️ No shell here — `' + badOp + '` is not interpreted. ' +
@@ -2870,7 +2875,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
return this._dispatch(cmd, args);
}
async _dispatch(cmd, args) {
- return this.terminal.dispatchCommand(cmd, args);
+ return this.terminal.dispatchCommand(cmd, args, this._currentRaw || null);
}
async _tryAIRoute(rawInput) {
// AI routing cooldown guard
@@ -4215,7 +4220,10 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
}
return trimmed;
}
- async dispatchCommand(cmd, args) {
+ // rawText (v4.6.130): the untokenised command line, when the call came from the
+ // terminal. Commands that need their delimiters (`js`, `node -e`) read it; every
+ // other command keeps using `args` and ignores the extra parameter.
+ async dispatchCommand(cmd, args, rawText) {
if (cmd === 'clear') { this.clearOutput(); return; }
if (cmd === 'expand') { this.toggleExpand(); return; }
if (cmd === 'sysmsg') { this.toggleSystemMessages(args[0]); return; }
@@ -4522,7 +4530,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
if (typeof _m.onCommandStart === 'function') { try { _m.onCommandStart(cmd, args); } catch (_e) {} }
}
}
- try { await mod.commands[cmd](args);
+ try { await mod.commands[cmd](args, rawText);
if (cmd !== 'tutorial' && cmd !== 'course') {
for (const _m of Object.values(this.moduleManager.modules)) {
if (typeof _m.onCommandExecuted === 'function') { try { _m.onCommandExecuted(cmd, args); } catch (_e) {} }
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 1d79b3a..ad6d0b7 100644
--- a/modules/core/module-loader.js
+++ b/modules/core/module-loader.js
@@ -4,7 +4,7 @@
// Core module bundle loaded synchronously in main.html.
// This loader only handles version cache-busting, splash, terminal init, and optional ext modules.
- const LATEST_VER = 'v4.6.128';
+ const LATEST_VER = 'v4.6.130';
const CACHE = '?v=' + LATEST_VER;
// Exposed so ext modules can register their own URL with the same cache-busting version
// instead of an unversioned path the browser may already have cached (shell.js did).
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index 76406b5..16e2c7e 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -196,6 +196,11 @@
return null;
}
async checkAndRun(rawInput) {
+ // v4.6.130: keep the untokenised line for the command itself. _parseArgs turns
+ // quote characters into delimiters and drops them, so `js "abc".toUpperCase()`
+ // reaches the module as `abc.toUpperCase()` and every string literal dies.
+ // Security classification still runs on the tokenised form below.
+ this._currentRaw = rawInput;
const badOp = this.detectComposition(rawInput);
if (badOp) {
this.terminal.print('⚠️ No shell here — `' + badOp + '` is not interpreted. ' +
@@ -278,7 +283,7 @@
return this._dispatch(cmd, args);
}
async _dispatch(cmd, args) {
- return this.terminal.dispatchCommand(cmd, args);
+ return this.terminal.dispatchCommand(cmd, args, this._currentRaw || null);
}
async _tryAIRoute(rawInput) {
// AI routing cooldown guard
@@ -1623,7 +1628,10 @@
}
return trimmed;
}
- async dispatchCommand(cmd, args) {
+ // rawText (v4.6.130): the untokenised command line, when the call came from the
+ // terminal. Commands that need their delimiters (`js`, `node -e`) read it; every
+ // other command keeps using `args` and ignores the extra parameter.
+ async dispatchCommand(cmd, args, rawText) {
if (cmd === 'clear') { this.clearOutput(); return; }
if (cmd === 'expand') { this.toggleExpand(); return; }
if (cmd === 'sysmsg') { this.toggleSystemMessages(args[0]); return; }
@@ -1930,7 +1938,7 @@
if (typeof _m.onCommandStart === 'function') { try { _m.onCommandStart(cmd, args); } catch (_e) {} }
}
}
- try { await mod.commands[cmd](args);
+ try { await mod.commands[cmd](args, rawText);
if (cmd !== 'tutorial' && cmd !== 'course') {
for (const _m of Object.values(this.moduleManager.modules)) {
if (typeof _m.onCommandExecuted === 'function') { try { _m.onCommandExecuted(cmd, args); } catch (_e) {} }