aboutsummaryrefslogtreecommitdiff
path: root/modules/ext
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/ext
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/ext')
-rw-r--r--modules/ext/console.js26
1 files changed, 21 insertions, 5 deletions
diff --git a/modules/ext/console.js b/modules/ext/console.js
index 60f75a7..f2731f1 100644
--- a/modules/ext/console.js
+++ b/modules/ext/console.js
@@ -14,12 +14,17 @@ class JSConsoleModule {
}
registerCommands() {
- this.commands['js'] = (args) => this.evalJS(args);
+ // rawText is the untokenised line from the dispatcher (v4.6.130): `js "abc".toUpperCase()`
+ // must reach eval with its quotes, and _parseArgs deletes them.
+ this.commands['js'] = (args, rawText) => this.evalJS(args, rawText);
this.commands['js-run'] = (args) => this.evalJSFile(args);
this.commands['node'] = (args) => {
// Bridge: AI hallucinates `node script.js` — map to js-run
if (!args[0]) return this.terminal.print('Usage: node <script.js> (alias for js-run)', 'warning');
- // Handle `node -e "expr"` — inline eval like Node.js
+ // Handle `node -e "expr"` — inline eval like Node.js. No rawText here on purpose:
+ // _parseArgs keeps `'…'` inside `"…"` verbatim, so `args` already carries the
+ // expression, whereas the raw form would hand eval `"import('fs')"` (a string
+ // literal) instead of `import('fs')` (an import). Measured v4.6.130.
if (args[0] === '-e') {
return this.evalJS(args.slice(1));
}
@@ -27,9 +32,19 @@ class JSConsoleModule {
};
}
- evalJS(args) {
+ // The expression to evaluate: the raw remainder when the terminal provided one,
+ // otherwise the tokenised arguments (AI tool calls, script runner, direct calls).
+ _expr(args, rawText) {
+ if (typeof rawText === 'string') {
+ const rest = rawText.trim().replace(/^\S+\s*/, ''); // drop the command word
+ if (rest) return rest;
+ }
+ return (args || []).join(' ');
+ }
+
+ evalJS(args, rawText) {
if (!args[0]) return this.terminal.print('Usage: js <expression>', 'warning');
- const expr = args.join(' ');
+ const expr = this._expr(args, rawText);
// v4.6.128: Sandboxed eval — restrict access to dangerous globals, still allows math/JSON/Array etc.
try {
const result = (function() {
@@ -68,7 +83,8 @@ class JSConsoleModule {
_printError(e) {
const msg = (e && e.message) ? e.message : String(e);
this.terminal.print(`Error: ${msg}`, 'error');
- const m = /(?:Failed to resolve module specifier|Cannot find module) '([^']+)'/.exec(msg);
+ const m = /(?:Failed to resolve module specifier|Cannot find module) '([^']+)'/.exec(msg)
+ || /No such built-in module: ?([\w@/.:-]+)/.exec(msg);
if (m) {
this.terminal.print(`'${m[1]}' is a Node built-in; the browser has no Node — ` +
`use the VFS (ls/cat/run, terminal.fs) instead`, 'warning');