| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
asking a model (v4.6.135)
`[ -f "/qt.txt" ] && echo exists` — the probe that closed the v4.6.132 batch — was not a shell test
at all: both commands were unregistered, so the line fell through to the AI fallback and a model was
asked whether a file exists. `_evalTest()` has implemented `-f -d -e -z -n ! = !=` for the if/while
parser since the script runner was written; only the command was missing.
* `test` and `[` registered in ShellModule. Success resolves silently; false rejects with a
`silent` error so `&&`/`||` skip it — POSIX prints nothing on exit 1.
* `[` requires the closing bracket (`[ : missing ']'`), and that message is NOT silent.
* `test` already names the on-demand regression-suite runner ("test auto" / "test list" in
help.html). A argv that does not look like a test expression (`-f`/`-d`/`-e`/`-z`/`-n`/`!`,
or `a = b` / `a != b`) is delegated to the handler registered before us, so `test auto`
still runs the suite and the two meanings cannot collide.
* terminal.dispatchCommand() now honours `err.silent` — without it a top-level `test -f /nope`
printed `Error in ShellModule.test:` (measured on v4.6.134, fixed in v4.6.135).
* help.html documents the bracket form next to the existing `test` row.
Three tags were minted minutes apart — v4.6.133 (commands), v4.6.134 (suite delegation),
v4.6.135 (silent top-level) — each a real cache-bust for a fix that superseded the previous build;
the net change is this commit.
Verification
node modules/test/test-shell-test-cmd.js -> 24/24 pass (RED against HEAD: "test command
registered" fails, "m.commands.test is not a function")
node modules/test/test-pipe-guard.js -> all seven flags true
node modules/test/test-parse-args.js -> 28/28 · daily job -> node tests 22/22 pass
newos4-maintain.py -> atlas 355 commands (was 354), modules-index 349, help 364/364
live v4.6.135, read from the terminal DOM:
[ -f /qt.txt ] && echo F135 -> F135
test -f /nope || echo M135 -> M135
[ -d /nope ] && echo BAD || echo N134-> N134 (false branch skipped, nothing printed)
test -> no output (suite not loaded; POSIX false)
test -f /nope -> no output
js "a b".length | wc -c -> 1 (v4.6.132 pipe fix still intact)
The AI fallback is no longer reachable for a bracketed file test.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
v4.6.131 taught the pipe stages to forward their raw text, but the shell's own wrapper still split
each stage with `cmdText.split(/\s+/)` — so `grep "alpha beta" /qt.txt | wc -l` handed grep the two
args `"alpha` and `beta"` and matched nothing. Measured live: the wrapper (ShellModule.init() replaces
terminal.executePipeChain) is the implementation that runs, so the core fix alone is decorative.
All six command-line split sites in modules/ext/shell.js now go through one helper, `_tokenize()`,
which delegates to `security._parseArgs` — the parser the bare-command path already uses — so the two
cannot drift again:
111 single command in a `&&` / `;` chain -> `grep "a b" f ; echo done`
176 pipe stage (the wrapper that runs) -> `grep "a b" f | wc -l`
358 `<` redirect input -> `cat < /f`
401 pipe chain inside the script runner -> also forwards the stage's raw text now
424 single command in the script runner -> also forwards raw text
778 `[ -f "path" ]` / `test -f "path"` -> quoted paths in test expressions
Empty stages are skipped instead of being dispatched as a command named '' (`help | | wc -l`).
The guard test grew the assertion that would have caught this: test-pipe-guard.js now checks
`shellTokensQuoted` (tokeniser used, no `cmdText.split(`, empty-stage guard present) alongside
`shellForwardsRaw`; both are RED against HEAD's shell.js.
Verification
node modules/test/test-pipe-guard.js -> {"empty":[],"blank":[],"classifyGuarded":true,
"pipeGuarded":true,"shellForwardsRaw":true,"shellTokensQuoted":true}
node modules/test/test-parse-args.js -> 28/28 · daily job -> node tests 21/21 pass
newos4-maintain.py -> maintenance result: OK (index 349, help 363/363, changelog v4.6.132)
live v4.6.132, read from the terminal DOM (no print monkey-patch):
cat /qt.txt -> alpha beta
grep "alpha beta" /qt.txt -> 1 match, 1 file
grep "alpha beta" /qt.txt | wc -l -> 2 (match line + summary; 1 when the quotes are lost)
grep "alpha beta" /qt.txt ; echo done-> match count + done
ShellModule._tokenize(c) === security._parseArgs(c) for 5 sample lines -> true
_tokenize('') / _tokenize(' ') -> [] / []
Also closes the last open feedback item: RPT-20261027-160751 ("Connection failed on all providers")
-> resolve, recorded in decisions.jsonl with evidence (headless probe URL `?probe=ctx`, v4.6.117,
single occurrence, no stack; Ollama answers 200 with 37 models now). Feedback queue: awaiting 0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
status (v4.6.131)
Four items closed in one batch; three ran as parallel subagents on disjoint files, the bundle was
rebuilt once by the parent (rebuild-bundles.py serialises the working tree, which carries other
sessions' work — see 161372f).
1. PIPE STAGES KEEP THEIR QUOTES. `js "a b".length | wc -c` reached eval as `a b.length` and printed
a ReferenceError. terminal.js executePipeChain() now keeps `stages[i]` aligned 1:1 with
`pipeParts[i]` (the `.filter(argv => argv.length > 0)` shifted the index; replaced by a lastStage
scan + `if (argv.length === 0) continue`) and forwards the stage's own text to the dispatcher.
The core path alone was dead code: ShellModule.init() replaces terminal.executePipeChain
(shell.js:144), and that override is what runs in the browser (`executePipeChain.toString()`
contained `cmdText.split`, not `lastStage`). Both now pass `cmdText`/`pipeParts[i]`.
test-pipe-guard.js was rewritten for the new shape and now also asserts the shell override
forwards raw text — the assertion that would have caught the shadowing. RED on HEAD: both flags
false; GREEN now.
2. MAINTENANCE --fix CAN CLEAR ITS OWN WARN. check_index() ran before check_atlas(), which is what
regenerates course/atlas.json, so --fix rebuilt modules-index.json from the stale atlas and the
same WARN reappeared every run. check_atlas() now runs immediately before check_index().
modules-index.json regenerated: cost/usage present, 349 commands, version 2026-10-29-atlas.
3. `cost` DOCUMENTED. modules/core/module-billing.js registers `cost [on|off]` (an alias of `usage`);
help.html had no row. Added above the `usage` row. help coverage 362/363 -> 363/363.
4. SHIPPED PLANS STOP ASKING FOR A DECISION. The applier only flips review<->safe, so every executed
review-gated plan kept counting as an open decision (5 of 5). newos4-feedback-collect.py now reads
the body: a `## Status` / `## Implementation` heading followed by shipped|executed plus a version,
commit or date is `status: done`; those stay in `plans` (visible as shipped) but leave
plans_review and awaiting_decision. Counts: plans_review 5 -> 0, awaiting_decision 4 -> 1
(the remaining one is a real open report, RPT-20261027-160751).
admin/feedback.html read `it.status[0]`, which would render `d` for a string status; it now
prefers `status_lines[0]`.
Also carried, authored by earlier uncommitted sessions and already live in the bundle (attributed,
not silently absorbed): modules/core/module-billing.js (new, BillingModule: cost/usage),
its CORE_FILES entry in scripts/rebuild-bundles.py, and the BillingModule registry entry in
terminal.js. The bundle was rebuilt from HEAD + this batch with ollama-manager.js taken from HEAD,
so another session's in-flight `_httpError` refactor is deliberately NOT in the shipped bundle
(grep -c _httpError core-bundle.js = 0).
Verification
node modules/test/test-pipe-guard.js -> {"empty":[],"blank":[],"classifyGuarded":true,"pipeGuarded":true,"shellForwardsRaw":true}
node modules/test/test-parse-args.js -> 28/28 pass
maintained job -> node tests 21/21 pass
scripts/newos4-maintain.py -> maintenance result: OK (modules-index 349, help 363/363,
changelog current v4.6.131)
live v4.6.131 (real DOM, no print monkey-patch):
js "a b".length | wc -c -> 1 (js printed 3, one byte)
js "a b".length -> 3
js "abc".toUpperCase() -> ABC
echo "two words" -> two words
node -e "import('fs')" -> red error + Node-builtin hint
js 6*7 -> 42
unhandledrejection -> 0
livePipeImpl: shell-override, livePipeForwardsRaw: true, costRegistered: true
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Found by running the shipped runner the way a user would, not by re-reading it.
- _evalTest() stripped [ ] but not the `test` keyword, so `if test -f /work/alpha.txt` evaluated
the word "test" as an operator, returned false, and every such branch took the else arm —
measured live: "alpha missing" for a file that exists. Both spellings work now
(`test -f x`, `[ -f x ]`), plus `-d`, `-e`, `-z`, `-n`, `!` and the = / != comparisons.
- `for f in /work/*.txt` ran ONCE with $f set to the literal pattern, because only command
arguments went through _expandGlobs. Loop items are expanded per iteration now, so the body runs
once per matched file with the real path.
- _expandGlobs() falls back to readDir() when a fs has no ls(), so the helper works against either
shape instead of silently returning the literal pattern.
Verification, live on v4.6.119:
- for+glob: "--- /work/alpha.txt / 3 /work/alpha.txt / --- /work/beta.txt / 1 /work/beta.txt"
(6 statements, exit 0) — previously one iteration printing the pattern.
- conditionals: "alpha exists", "nope missing", grep + $? intact.
- end-to-end /work/report.sh (loop + glob + >> append + test -f) wrote a correct log:
"start / file: /work/alpha.txt / 3 /work/alpha.txt / file: /work/beta.txt / 1 /work/beta.txt …".
- unknown command still reports "command not found" and never calls the AI.
- test-shell-script.js 27 -> 35 asserts.
Retracted: I reported `echo x > file` as broken. It was my probe monkey-patching terminal.print,
which bypassed the capture stack the redirect uses. Both paths write correctly:
direct helper -> "direct-call", prompt -> "prompt-call\n".
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The stdin work in 5cf5010 fixed wc/head/tail but left grep alone, and grep's no-operand branch
sweeps the cwd. Measured on v4.6.115: `cat /gdir/in.txt | grep zzz` answered
`other.txt:1: zzz in a cwd file` — a file that was never piped, and `cat in.txt | grep bbb`
reported the match twice (`in.txt` from the cwd sweep plus `__pipebuf__` from the injected file).
- module-fs.js: when terminal._pipeInput is set and no file operand was given, grep searches the
staged text and prints `<line>: <text>` (stdin has no filename, as in GNU). The cwd sweep stays
for a bare interactive `grep x`.
- shell.js: _isPipeAware now covers only sort/cut/uniq/expand. grep/head/tail/wc read _pipeInput
themselves, so injecting the __pipebuf__ file produced duplicate hits and named stdin results.
Verification:
- test-shell-stdin.js 20 asserts (+4 for grep: no cwd match, non-match reports none, match prints
no filename, -c counts stdin). RED on HEAD: G1/G2/G3 fail with `other.txt:1: zzz in a cwd file`.
- Suite 18/18 files; maintain --fix "node tests 18/18 pass".
- Live v4.6.116 after `cd /gdir` (where other.txt contains zzz):
`cat /gdir/in.txt | grep zzz` -> (no matches) · `| grep bbb` -> `2: bbb` · `| grep -c b` -> 1 ·
`| head -n 2` -> aaa,bbb · `| tail -n 1` -> ccc · `| wc -l` -> 3.
Credit: the false-positive grep was reported by the stdin subagent as an aside; re-derived here
against the live terminal before fixing.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
globs, redirects, stdin
MAINT-021 and MAINT-017, executed from .hermes/plans/2026-10-27_145500-newos4-script-runner.md.
- shell.js: new block parser (_parseBlocks) and executor (_runInstructions/_runSimple/_runCommand/
_dispatchOne). `for`/`while`/`if` bodies now run, including one-line `if [ -f x ]; then y; fi`.
Variables expand through _expandVars ($VAR, ${VAR}, $?, single quotes literal), globs expand
through _expandGlobs (unquoted args only, per-command), and `>`/`>>` capture the command's output
into the VFS instead of being passed to the command as text.
- shell.js: a script never answers an unknown command by calling the AI any more. It prints
`script:<path>:<line>: <cmd>: command not found` and sets $? — the AI fallback stays for typing.
- shell.js: unclosed blocks and bad `for` headers surface as `Script <path> failed: …` (the parse
used to sit outside the try, so a bad script crashed the runner).
- shell.js: interactive path gets the same treatment — prompt globbing and `echo hi > /f` now work.
Cause of the asymmetry: the two code paths had separate dispatch.
- shell.js: `_isPipeAware` no longer injects the __pipebuf__ file into wc (it reads _pipeInput), and
the module registers itself with a versioned URL and calls its own init() — registerModule() does
not call init(), so the executeCommand wrapper was never installed from the boot bundle.
- module-fs.js: wc/head/tail/grep read piped stdin when no file operand is given; `cat` accepts
several operands (`cat *.txt`); `ls <file>` names the file instead of printing "(empty)".
- main.html: shell.js removed from the extension list — it is already inside
critical-ext-bundle.js, and loading it twice produced two ShellModule instances that could
disagree about their version.
Verification:
- test-shell-script.js 27 asserts (parser, variables, globs, executor, redirect, command-not-found,
one-line if, parse error). RED on HEAD: `TypeError: shell._parseBlocks is not a function`.
- test-shell-stdin.js 16 asserts (added by a subagent): wc -l/-c, head -n, tail -n over stdin, and
the file-argument path unchanged.
- Suite 18/18 files; maintain --fix "node tests 18/18 pass".
- Live on v4.6.115, through the real executeCommand:
`ls /n*.txt` -> /n1.txt · `cat /n*.txt` -> one|two · `echo hi > /r.txt` then `cat /r.txt` -> hi ·
`cat /probe.txt | wc -l` -> 3 (bare, no __pipebuf__) ·
`script /run.sh` -> item 1 / item 2 / item 3 / exists / command not found, exit 1, no AI routing.
|
| |
|
|
| |
sed in safeRead, security test harness (21 checks)
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|