<feed xmlns='http://www.w3.org/2005/Atom'>
<title>newos4/scripts, branch v4.6.156</title>
<subtitle>newOS4 - the browser terminal that runs WebX11 user spaces</subtitle>
<id>https://newos.webx11.duckdns.org/newos4/atom?h=v4.6.156</id>
<link rel='self' href='https://newos.webx11.duckdns.org/newos4/atom?h=v4.6.156'/>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/'/>
<updated>2026-11-07T17:19:47+00:00</updated>
<entry>
<title>feat(newos4): six interface modes (easy -&gt; cockpit) over one terminal</title>
<updated>2026-11-07T17:19:47+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-07T17:19:47+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=aac2b1c0689aefeb140e08c24278ec486611fe10'/>
<id>urn:sha1:aac2b1c0689aefeb140e08c24278ec486611fe10</id>
<content type='text'>
Symptom: newOS4 had exactly one layout. A first-time user had no way in that did
not require learning commands, and an operator had no surface showing live runtime
state; the only way to see heap/VFS/tool-call telemetry was to type cat /sys/...

Root cause: no UI layer existed. main.html hard-codes one flex column, and
terminal.js binds the stock nodes by id at construction, so an alternative layout
had to either duplicate the output stream or reparent the real nodes.

Fix:
- modules/core/module-uishell.js (new): mode registry, #ui-root, stock-node
  snapshot/restore, identity check that reverts to classic when a mode removes or
  clones #terminal-container, shell-docked #ui-foot (the AI stop button exists in
  every mode), ui command, gated first-run picker, ?ui= honoured only if the mode
  actually registers, lazy mode loading via the same loader ext modules use.
- modules/ext/ui-{easy,chat,studio,workspace,commander,cockpit}.js (new): six
  reparenting skins with scoped CSS. Workspace/commander/cockpit read real /sys/*
  mounts and captured command output; no value on screen is invented.
- modules/test/test-ui-shell.js (new): 25 assertions - teardown, clone rejection,
  failed-mount fallback, footer docking, destructive-mode rejection.
- terminal.js: register the shell; make one failing ext module stop aborting the
  rest of the serial load loop (it did, silently, which is why six ui-* files at
  the end of extKeys never loaded).
- module-loader.js: LATEST_VER v4.6.152. The six ui-* keys are deliberately NOT
  in extKeys - see the rate-limit note below.
- admin/scripts/probe-ui-modes.py (new): 6 modes x 2 viewports plus a --cycle pass
  that switches all seven states on ONE page and asserts the output node keeps its
  identity.

Also fixed, found while testing (pre-existing, production-affecting):
- nginx limit_req zone=webx11 burst 50 -&gt; 200. One newOS4 boot fans out ~40 assets;
  the bucket emptied mid-boot and every later module got 503. Measured in
  webx11.error.log: "limiting requests, excess: 50.696 by zone webx11" against
  modules/ext/ui-chat.js. A reload within ~50 s silently broke modules. Sustained
  rate unchanged at 60r/m.

Verified: node modules/test/test-ui-shell.js -&gt; 25/25; removing both _restoreStock()
calls measures 20/25 with five FAILs; probe-ui-modes.py --cycle against production -&gt;
ALL PASS, 13 checks; served bytes: v4.6.152 x3 in main.html, class UIShellModule x1
in core-bundle.js, six ui-* packages in packages.json.
</content>
</entry>
<entry>
<title>fix(changelog): keep the newest commits of a build; wrap long detail URLs</title>
<updated>2026-11-06T10:40:54+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-06T10:40:54+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=7bdcd58762c60c9501657b9df2983999f276f8b1'/>
<id>urn:sha1:7bdcd58762c60c9501657b9df2983999f276f8b1</id>
<content type='text'>
Symptom: after bodies were rendered, this fix's own commit was missing from
its own entry, and a long URL inside an expanded detail was clipped at the
right edge on a 390 px screen.

Cause: items took group[:MAX_ITEMS] from a CHRONOLOGICAL list, so a 17-commit
build showed its first 14 and dropped the newest three (the count line said
"…and 3 more", hiding which three). And the injected detail CSS set no
overflow-wrap, so a 47-character URL pushed the paragraph to 320 px of
unbreakable text — the document stayed at 0 overflow only because the li
clipped it.

Fix: show group[-MAX_ITEMS:] and label the omitted ones "…and N earlier
commit(s)"; entry.commits follows the same slice. overflow-wrap:anywhere on
detail p/ul. The CSS block is now re-emitted from DETAIL_CSS behind
BEGIN/END sentinels instead of being write-once, and the first version's
sentinel-less block is stripped on sight so no page keeps two copies.

Verified: 390 px — p scrollWidth == clientWidth == 320, document overflow 0,
URL wraps; newest entry lists c759c50 and this commit; island 83 entries,
14 commit chips matching 14 shown items; CHANGELOG.md byte-identical on a
second run.
</content>
</entry>
<entry>
<title>fix(changelog): render the commit body, not just the subject</title>
<updated>2026-11-06T10:37:11+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-06T10:37:11+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=1983ce5796e45b312b142554cb717db7f8978bc8'/>
<id>urn:sha1:1983ce5796e45b312b142554cb717db7f8978bc8</id>
<content type='text'>
Symptom: the mirrored CHANGELOG.md (and the page) showed one-line commit
summaries while README promises "every shipped fix, symptom -&gt; cause -&gt; fix -&gt;
verified". A reviewer measured the gap from the public mirror and could only
conclude the claim overpromised.

Cause: scripts/build-changelog.py:59 formatted commits with %s only; %b was
never read, so the verification prose every fix commit carries (5b55af9 is a
typical one) was discarded before rendering. The MD writer then replaced every
tag with a backtick, so nothing richer could have survived anyway.

Fix: read %b with a %x02 terminator (line-based parser, multi-line body), split
it into paragraph/bullet blocks capped at 900 chars with a visible marker, and
render it twice from one source: as a native &lt;details&gt; per item on the page
(its renderer already assigns item HTML, so no JS change; CSS injected once
behind a sentinel) and as nested bullets under the line in CHANGELOG.md. The
"every build" gap note and the page dek were rewritten to describe what the
file now contains.

Verified: page 100 details render, first opens 23 -&gt; 232 px with 3 paragraphs,
no horizontal overflow, island parses (83 entries); two consecutive runs are
byte-identical for CHANGELOG.md; the double-escape bug this surfaced (code
spans shipped as &amp;amp;lt;sha&amp;amp;gt;) is fixed and grep for &amp;amp;lt; is 0.
</content>
</entry>
<entry>
<title>fix(changelog): stop prepending the preamble on every run</title>
<updated>2026-11-05T19:40:17+00:00</updated>
<author>
<name>WebX11 SysOp</name>
<email>darkmotox11@gmail.com</email>
</author>
<published>2026-11-05T19:40:17+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=842357847dee369da41e690a3069905200adf124'/>
<id>urn:sha1:842357847dee369da41e690a3069905200adf124</id>
<content type='text'>
write_md() put the H1 + description inside the generated body and also kept
old[:m.start()], so each regeneration added another copy: 44 runs left 44 H1s and
pushed the first release to line 179. Only the region between the markers is
generated now, an existing file with N copies is collapsed to one, and an assert
refuses to write a file containing two generated regions (proven RED by injecting
a duplicate). Regenerated: 83 entries, 185 commits, first heading v4.6.146 (was
v4.6.135 - the file had not been committed since 2026-11-02).
</content>
</entry>
<entry>
<title>feat(newos4): configurable tool budgets, per-model defaults, real tool telemetry</title>
<updated>2026-11-02T19:46:18+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-02T19:46:18+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=44e390f3e08acb3ad13daea0dbf365424eeb7d95'/>
<id>urn:sha1:44e390f3e08acb3ad13daea0dbf365424eeb7d95</id>
<content type='text'>
Closes STEER-20261102-201234-7200 (read_file capped at 6000 while the schema
promised 32000).

Budgets (module-aiassist.js + new modules/core/model-profiles.js):
- one resolver, precedence: call arg -&gt; loop profile -&gt; model profile -&gt;
  ai-config -&gt; code default. Keys: read-page-chars, read-result-chars,
  tool-context-budget, fetch-chars.
- model classes tiny/small/mid/large keyed by id then by size token; `mid`
  reproduces the old numbers so nothing moves for a mid model.
- _clipToolResult now clips the `content` field, never the serialised JSON
  envelope: clipping the envelope deleted truncated/next_offset, so a 16k page
  lost its continuation pointer (the reported symptom).
- read_file's schema no longer promises a number the model class may not give.

Telemetry:
- _recordToolCall keeps sizes, duration, ok and a 120-char arg preview instead
  of the whole payload; ai-stats prints calls / ok% / avg ms / KB / truncation
  from localStorage webx11_tool_stats.

Server (api/proxy-api.py):
- /api/extract honours max_chars (default 12000, cap 100000) and returns
  content_chars + truncated; the bs4 branch is no longer uncapped while the
  regex fallback was silently cut at 10 000.

BEHAVIOUR CHANGE: num_ctx for a model with no explicit setting now follows the
class default (16384 -&gt; 65536 for cloud/large, 8192 for tiny). Cloud models bill
on tokens used, so cost is unchanged; local models get a bigger KV cache.

Two build traps, both now documented in AGENTS.md:
- model-profiles.js must be the FIRST entry in CORE_FILES and must export the
  MODEL_PROFILES global (a TDZ typeof throws; without the global every budget
  silently falls back to the code default).
- a budget key needs the setter branch AND the constructor loader at
  module-aiassist.js:49 - persisting without restoring looks fine until reload.

Verified: test-tool-context-bound 23/23 (RED before: E1-E4 + TypeError on
_toolLimit); live bundle read_page 32000 / result 12000 / budget 48000 /
fetch 12000 and _ctxTokens 65536 for gemma4:31b-cloud; /api/extract 28970 -&gt;
500 chars with truncated true; ai-config write+restore probe 6/6.
</content>
</entry>
<entry>
<title>feat(feedback): chat plane on the dashboard + honest triage ranking</title>
<updated>2026-11-02T19:31:05+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-02T19:31:05+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=431cef232d2804c0568ff3f3c1e95caaa6be6870'/>
<id>urn:sha1:431cef232d2804c0568ff3f3c1e95caaa6be6870</id>
<content type='text'>
Tasks 3-8 of the chat-plane plan:
- cron newos4-chat-ingest (0cc4bb4d2f82, */10, script-only, silent when idle)
- collect.py: plan_key() normalises the two ledger spellings on read (5 rows
  carried a plan name without .md and were silently unmatched); per-item
  bulk_pass for the 2026-10-26 pass; counts.chat_total
- feedback.html: 5th tile "from chat"; card shows who closed an item and when,
  with a bulk marker
- triage: UNFILEABLE class -&gt; top_real now ranks actionable errors only
  (first row went from an 18 203 fetch failure to the 989 toLowerCase runtime
  error); network/abort signatures move to policy_noise
- AGENTS.md: the chat plane documented as an intake

Verified: autoreview --dry-run over the 5 chat reports -&gt; "nothing provable
(5 left for the operator)" (R6 refuses auto:false); maintain 16 PASS 0 FAIL;
feedback.html inline JS node --check OK; collect -&gt; awaiting 5, chat_total 5.
</content>
</entry>
<entry>
<title>feat(newos4): ingest report-like chat messages into the feedback queue</title>
<updated>2026-11-02T19:26:53+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-02T19:26:53+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=f66daca1c8866acc19aab5ea0e94eeaa3d8012e6'/>
<id>urn:sha1:f66daca1c8866acc19aab5ea0e94eeaa3d8012e6</id>
<content type='text'>
The chat plane (api/chat/post.php -&gt; data/chat/*.json) was read by nothing in the
feedback pipeline: 22 report-like messages over 11 days produced 0 queue items.
This adds scripts/newos4-chat-ingest.py - a read-only scanner by default, --apply
writes RPT-* files in the report.php schema with diagnostics.auto=false so the
auto-reviewer leaves them for a human, deduped on (thread, msg_id).

First apply filed 5 items (2 x 2026-10-28, 3 x 2026-11-02); snapshot went
reports_total 23 -&gt; 28, awaiting_decision 0 -&gt; 5. Second run is silent.
</content>
</entry>
<entry>
<title>feat(newos4): R5 stale-artefact rule + dashboard queues a decision once</title>
<updated>2026-11-02T19:05:14+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-02T19:05:14+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=bfd5e9567fbfb84b8494b2f18915e75f1ebb3c04'/>
<id>urn:sha1:bfd5e9567fbfb84b8494b2f18915e75f1ebb3c04</id>
<content type='text'>
Feedback queue triage 2026-11-02: 23 reports, 22 decided, 1 open -- and the open
one (RPT-20261029-115241, "[Terminal] Error in ShellModule.test: {\"silent\":true}")
was already fixed in v4.6.135 (4472c81, modules/core/terminal.js:1966) yet could
never be cleared by automation.

Root cause: identifiers() keeps only generic class names (Terminal, ShellModule),
both of which necessarily stay in the code, so R2 (every identifier absent) cannot
fire; R4 needs a stack and this report has none. A stack-less
"Class.method: {json}" console.error whose emitter gets *guarded* -- rather than
deleted -- therefore sits "awaiting decision" forever.

- newos4-feedback-autoreview.py: new R5 stale artefact -- auto report, no stack,
  every error count &lt;= 1, and nothing newer than STALE_DAYS=5 in
  data/debug/errors.json -&gt; resolve with the measured quiet_days as evidence.
  New --errors &lt;path&gt; knob so the rule can be run against fixtures.
  RED (pristine copy): "nothing provable (1 left for the operator)"
  GREEN (aged fixture): "resolve ... quiet 5.3d -- reopen if it recurs"
  NEG (live store 4.3d, and a report with count 21): both still refuse.
- admin/feedback.html: markDecided() patches the local snapshot, so a queued
  decision leaves the default "Awaiting only" list immediately instead of after
  the 10-minute sync rebuild. Measured cause of 3x approve on one plan in 3 min
  (10-30 16:03/16:05/16:05) and 6 items with duplicate rows.
- AGENTS.md: documents R5 and the fixture knob (also carries the already-live
  2026-11-02 cookie-notice paragraph that was pending in the tree).
</content>
</entry>
<entry>
<title>fix(newos4): pipe stage quoting, maintain ordering, help cost row, plan status (v4.6.131)</title>
<updated>2026-10-29T11:42:30+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-10-29T11:42:30+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=320877d2c8a3bb6be3d32780ecfc6e7d14528f66'/>
<id>urn:sha1:320877d2c8a3bb6be3d32780ecfc6e7d14528f66</id>
<content type='text'>
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 =&gt; argv.length &gt; 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 -&gt; 363/363.

4. SHIPPED PLANS STOP ASKING FOR A DECISION. The applier only flips review&lt;-&gt;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 -&gt; 0, awaiting_decision 4 -&gt; 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   -&gt; {"empty":[],"blank":[],"classifyGuarded":true,"pipeGuarded":true,"shellForwardsRaw":true}
  node modules/test/test-parse-args.js   -&gt; 28/28 pass
  maintained job                          -&gt; node tests 21/21 pass
  scripts/newos4-maintain.py             -&gt; 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   -&gt; 1        (js printed 3, one byte)
    js "a b".length           -&gt; 3
    js "abc".toUpperCase()    -&gt; ABC
    echo "two words"          -&gt; two words
    node -e "import('fs')"    -&gt; red error + Node-builtin hint
    js 6*7                    -&gt; 42
    unhandledrejection        -&gt; 0
  livePipeImpl: shell-override, livePipeForwardsRaw: true, costRegistered: true
</content>
</entry>
<entry>
<title>fix(docs): apply the GLM-5.3 review to the changelog pipeline</title>
<updated>2026-10-27T16:42:46+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-10-27T16:42:46+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=cc9d0e34b9280b05fa4428d59c8dc16311091758'/>
<id>urn:sha1:cc9d0e34b9280b05fa4428d59c8dc16311091758</id>
<content type='text'>
Verdict was APPROVE WITH CHANGES: 21 findings, 17 fixed, 3 refuted with the command that killed
them, 1 kept with the audit that justifies it. The reviewer's best find was a design bug of mine,
not a typo.

- The age guard was unfixable by --fix and fired on a quiet week. check_changelog() regenerated only
  on a version mismatch, then returned from the version-match branch before the age check could act,
  so a page older than the threshold stayed red forever with the false diagnosis "the daily job is
  not running". Now --fix always regenerates (0.84 s) and age is read from a new `checked` stamp
  separate from `generated` (content-change time), which also stops a daily 65 KB rewrite of an
  unchanged page. Proved: seeded 641 h -&gt; FAIL + "run maintain --fix to refresh" -&gt; --fix -&gt; PASS.
- Atomic writes everywhere (temp + os.replace): nginx serves these files off disk, so an in-place
  truncate could hand a visitor half a page.
- The JSON island now escapes "&lt;/" (a commit subject containing &lt;/script&gt; would have closed it
  early and truncated the page). Proved by injecting exactly that subject: island still parses, no
  stray script tag, payload survives as data.
- Sibling drift is checked, not assumed: changelog.json's currentVersion is compared with
  LATEST_VER. Seeded v4.6.100 -&gt; FAIL "feed drifted", --fix repairs.
- Build tags are audited under --deep: 67 tags, each must be reachable from HEAD and name the
  version its commit tree carries. Proved by tampering (v9.9.9 on the root commit -&gt; FAIL) and by
  deleting it (-&gt; PASS).
- Feed: ids no longer hardcode 2026 or contain spaces, entries carry a &lt;link&gt;, timestamps are real
  iso-strict commit times instead of a fake T00:00:00Z on a local date, and summaries are truncated
  before escaping so no "&amp;l" tail. 0 ids with spaces, 0 broken entity tails.
- write_md refuses to overwrite a CHANGELOG.md that lost its markers; run() gained a 300 s timeout
  (a hung git used to block the whole 07:15 job); jumpToHash no longer stacks hint paragraphs;
  dead fields removed (KIND_ORDER, order, currentSynthesized, sourcesRead — each appeared exactly
  once in the page, i.e. only in the island the generator wrote).
- HTML nits: guide.html footer separator, empty "fixed in" line suppressed, help.html version
  bumped off v4.5.22. Header stat relabelled "Commits behind it" (it read "Bug records on disk"
  while counting commits — my own defect, not the reviewer's).
- Refuted: merge commits (repo has 0: `git log --merges | wc -l`), first-version board links (the 2
  multi-version notes both name the fixing build first), sources disclosure (the page already
  renders those module paths).

maintain --deep: node tests 20/20, changelog current, age 0.0h, 67 tags verified, result OK.
Mobile: ALL VIEWPORTS OK.
</content>
</entry>
</feed>
