diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-29 12:29:14 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-29 12:29:14 +0100 |
| commit | db65ce7628b0073c41ff1c13334723a043b24479 (patch) | |
| tree | 1fd5bc4711d7f385937a22b7ae5e58513e06c3f0 /modules/core/core-bundle.js | |
| parent | 43d067396b5a620441b9a299c45e7b0b129ccd15 (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/core-bundle.js')
| -rw-r--r-- | modules/core/core-bundle.js | 14 |
1 files changed, 11 insertions, 3 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) {} } |