aboutsummaryrefslogtreecommitdiff
path: root/modules/core/core-bundle.js
Commit message (Collapse)AuthorAgeFilesLines
* feat(newos4): the script runner runs scripts — control flow, variables, ↵Agando10 days1-11/+51
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* fix(newos4): the loop guard reads the config it documentsAgando10 days1-6/+24
| | | | | | | | | | | MAINT-004. The guard read warnAt/blockAt/hardStopAt while one config path wrote `threshold`, and `enabled` was never consulted anywhere — writing {"enabled": false} into webx11_ai_config changed nothing at all. One resolver, _loopPolicy(), now normalises both spellings, guarantees warnAt <= blockAt < hardStopAt, and both the block and the warning are gated on enabled. Live on v4.6.110 (_loopPolicy called in the page): defaults {warn 3, block 6, hardstop 9}; {threshold: 4} -> {warn 2, block 4, hardstop 7}; {enabled: false, threshold: 3} -> enabled false. test-loop-policy.js: 17 asserts, RED on HEAD ("_loopPolicy is not a function"), suite 16/16.
* fix(newos4): agent registration is absolute-path and honest about what it storedAgando10 days1-21/+64
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Three of the four agent-cluster items, each with a test that is red on HEAD. - MAINT-008 (module-agent-registry.js + module-orchestrator.js): registerAgent took args[0] as the agent id and only args[1] as the capability list, while both orchestrator callers passed the command word first — so the boot log read "Agent 'register' registered with 1 capabilities" and four capabilities were stored as one. The command word is now stripped, args[1..] are joined and split on commas (both "a,b" and "a b c" work), and the callers pass [id, ...caps]. - MAINT-007 (registry + aiassist): mkdir/rm used relative paths, so a registration made from /docs created /docs/agents/<id> and an unregister from any other cwd removed nothing — the leaked directories the report suspected. Every path in the registry (and the four /_sessions writes in module-aiassist.js) is absolute now. - MAINT-009 (listAgents): a directory alone counted as a registered agent, so a hand-made /agents/foo appeared as "foo — unknown (0 capabilities)". Only a readable status.json with an id counts now; the rest are reported as unregistered directories. Verification: test-agent-registry.js (11 asserts). RED on HEAD: 9 of 11 fail, including the exact `✅ Agent 'register' registered with 1 capabilities` line and `hand-made — unknown (0 capabilities)`. Suite 15/15 files green; maintain --fix "node tests 15/15 pass". Live on v4.6.109: boot prints "✅ Agent 'orchestrator-0' registered with 4 capabilities"; `agent-list` warns "1 directory(ies) in /agents have no status.json (not registered): hand-made"; register from / with a comma list stores 3; unregister from /docs removes the directory and leaves no /docs/agents. MAINT-006 stays open with a narrower note: the hard stop was only reachable when every call in a batch was blocked, so a mixed batch (one repeated + one novel call) never ended. The same ceiling now applies on the mixed path, but that branch lives inside agenticLoop and is not covered by a unit test — it needs a live agent run that repeats a call.
* fix(newos4): help names the owner of an undocumented command; loop lessons ↵Agando10 days1-17/+36
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | leave /skills Two board items plus the context-window close-out. - MAINT-019 (module-system.js): `help <cmd>` answered "No help available for X" for a command that is registered but has no help line, which reads as if the command does not exist. It now says "X — registered by <Module>, which publishes no help line for it. Try: help". A command that genuinely does not exist still gets the old message. - MAINT-020 (module-aiassist.js): auto-generated loop lessons were written into /skills with their own index entries, so `skill list` filled up with system noise. Lessons now live in /lessons with /lessons/index.json; the reader still falls back to legacy loop-lesson-* entries in /skills for one release, and the one-time migration deletes them from the skills index. Verification: test-help-and-lessons.js (10 asserts, includes a lint-style guard that no lesson is written into /skills). Suite 14/14 files green; maintain --fix "node tests 14/14 pass", "help coverage 362/362". Live on v4.6.108: `help agent-register` -> "registered by AgentRegistryModule, which publishes no help line for it"; `help a2a-prune` unchanged; `help nope-not-real` still "No help available". MAINT-001 close-out (no code change here): history is capped at 20 messages with a "[context trim]" marker (first trim at turn 11 in a 60-turn probe), token compression took 80 msgs/8080 tok to 17/2625, and since v4.6.107 one turn's tool bodies are clipped to 6 000 chars each and elided past 24 000 with the newest 4 kept verbatim. Residual, recorded on the board: maxContextTokens is never sent to the server as num_ctx, so the real window is whatever the endpoint defaults to. MAINT-018 close-out: not reproducible. Live probes on v4.6.107/108 — `sed -n '1,3p' /probe.txt` prints lines 1-3, `sed -n "2,4p"` prints 2-4, `grep 'line4'` matches, and the same commands inside `script /q.sh` print correctly. The "Command not found: for;" symptom belongs to the line-by-line script runner, which is MAINT-021 and stays open.
* fix(newos4): bound one turn's tool context, honest skill-create, quarantine ↵Agando10 days1-2/+67
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | a corrupt memory graph The three items the last review and probes left open, each with a test that is red on HEAD. - MAINT-023 (module-aiassist.js): one agentic turn grew without limit — every role:'tool' body was pushed verbatim and only this.context was trimmed BETWEEN turns (measured 6 -> 112 705 tokens across 8 model calls against maxContextTokens 12 000). Observations are now clipped to maxToolResultChars (6 000) with a note saying how much was withheld and to re-read with offset/limit, and _boundToolContext() elides the oldest tool bodies once the running total passes toolContextBudgetChars (24 000), always keeping the newest 4 verbatim. The ceiling is turn-independent: the same ~26 k chars at 8 turns and at 20. - MAINT-024 (modules/ext/skill-pack-core.js): the command printed "✅ Skill created" with fs.writeFile()'s boolean discarded (probe: fresh VFS, success printed, no file), stripped invalid characters instead of rejecting the name (My-Skill -> y-kill, DevOps_Thing -> evpshing), and treated an unreadable /path argument as the skill body. Now: kebab-case is validated (uppercase normalises and the final name is reported; anything else is refused), /skills is created on first use, the write and index results are checked, and an unreadable body path is an error. - MAINT-003 (module-memory-graph.js): a corrupt store looked exactly like a fresh install — fsReadJSON returns null on a parse failure, so 85 nodes could vanish with no warning and no copy. The probe confirmed the loss: seed 6 nodes, truncate nodes.json and export.json, reload -> 1 node, no message, nothing in .trash. _quarantineCorrupt() now keeps the unreadable file as <name>.corrupt, warns, and then loads from whatever remains. Tests: test-tool-context-bound.js (11), test-skill-create.js (16), test-memory-graph-recovery.js (8). RED on HEAD: tool-context crashes on _clipToolResult (exit 1), skill-create 16 assertions fail on the missing file/index, memory-graph B1/B2/C1 fail (no warning, no .corrupt copy). Suite now 13/13 files green; maintain --fix reports "node tests 13/13 pass". Live on v4.6.107: skill-create My-Skill -> ✅ "my-skill"; DevOps_Thing -> Invalid skill name; /does/not/exist.md -> Cannot read skill body; only my-skill in the index.
* fix(newos4): name the real Ollama failure, bound auto-connect, fix the ↵Agando10 days1-21/+70
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | review findings Second pass over v4.6.105, driven by an independent review that reproduced each item. - ollama-manager.js: chat() now reports the captured cause ("Ollama error: Failed to fetch", "…: HTTP 500") instead of the generic "No provider responded"; _lastChatError is reset per call so an old reason is never replayed. chatWithTools carries the reason in its error string. - ollama-manager.js: _ensureConnected() bounds connect() with a deadline (connectTimeoutMs, default 10 s). connect() uses a bare fetch(), so a blackholed endpoint previously left the shared in-flight promise pending forever and hung every later message — the old code failed fast. The attempt is now try/finally, so a throwing terminal.log() can no longer poison _connecting and suppress the retry on the next message. - ollama-manager.js: an automatic connect no longer silently rewrites the user's saved model; the previous choice is restored in memory and in localStorage, and the user is told. - ollama-manager.js: the DeepSeek empty-content branch returned top-level tool_calls with content '', so the caller pushed an assistant message with no tool_calls and then a role:'tool' reply — an orphan tool message, which is the HTTP 400 that branch exists to prevent. tool_calls now live inside the message as well. - module-aiassist.js: a no-argument list_files returned the ROOT instead of the cwd (readDir('/') instead of ls()); it now means the current directory again. Error text keeps the path as the caller wrote it, and write_file distinguishes a read-only virtual path (/sys/…) from a missing parent instead of blaming the parent. Tests: test-ollama-error-text.js (new, 14 asserts, RED 9/5 before the edit), test-ai-fs-tools.js 7 → 16 asserts (write onto a directory, /sys write, list a file, '/', no-arg cwd, relative path), test-ollama-autoconnect.js 11 → 19 (blackholed endpoint settles + retries, saved model survives; the old "connecting hint" assertion is now falsifiable — the reviewer showed it also matched connect()'s pre-existing line). Suite: 10/10 files green. Live on v4.6.106: healthy auto-connect 42 ms with the hint; blackholed endpoint settles in 1242 ms with connectTimeoutMs=1200, prints "timed out" and "unreachable", _connecting cleared.
* fix(newos4): connect to Ollama on first use, honest write_file, list_files ↵Agando10 days1-40/+52
| | | | | | | | | | | | | | | | | | | | | | | | | | | sees /sys Three defects, each re-probed first and covered by a test that is red on HEAD. - ollama-manager.js: nothing connects at boot; only the Ollama tab button called connect(), so a user's first message died with "Ollama not connected. Use ollamaconnect first." even when the endpoint was healthy. New _ensureConnected() runs from chat(), chatStream() and chatWithTools(): one attempt, shared by concurrent callers, a "Connecting…" hint, and only then the error — which now says the endpoint is unreachable instead of telling the user to run a command they already expected to work. - module-aiassist.js write_file tool: fs.writeFile()'s false return was dropped and the tool answered "File written: …" anyway, sending the model on to a read that found nothing (MAINT-012). It now returns "Write FAILED: … — parent directory missing or target is a directory; nothing was written". - module-aiassist.js list_files tool: the hand-rolled walk over root.content never resolved the virtual mounts, so /sys — a directory the UI and `ls` both show — answered "Directory not found" (MAINT-011). It now uses fs.readDir(), which resolves mounts and returns null for a genuinely missing path. Tests: modules/test/test-ai-fs-tools.js (7 asserts) and modules/test/test-ollama-autoconnect.js (11 asserts). RED on HEAD: ai tools 4 passed/3 FAILED (/sys → "Directory not found", write_file → "File written: …"), ollama 2 passed/9 FAILED ("Use ollamaconnect first"). GREEN now 7/7 and 11/11; maintain --fix reports "node tests 9/9 pass". Live on v4.6.105: connectedBefore=false, _ensureConnected()=true in 41 ms, 37 models.
* fix(newos4): never overwrite a directory node; ls honours a path after its flagsAgando10 days1-4/+25
| | | | | | | | | | | | | | | | | | | | | MAINT-010 and MAINT-016, the two high items the review left open. Both were re-probed before the change and the new test is red on HEAD (5 passed, 7 FAILED). - virtual-fs.js rename()/copy(): they assigned dst.dir.content[dst.name] with no destination type check, so `mv file /docs` replaced the /docs node and every child vanished while the command printed "Moved: …". Refuse when the destination is a real directory node (the virtual /sys entry is exempt). rename() also erased the file on `mv a a`, because assigning and then deleting the same key removes it; that path is now an explicit no-op. - module-fs.js mv/cp: a refusal used to surface as "No such file or directory", which sent the operator looking for a missing source. The destination is now inspected first and the message says "is a directory — refusing to overwrite it". - module-fs.js ls: the path was only read from argv[0], so `ls -l /work` listed the cwd. The first non-flag argument is now the target; the path-first form is unchanged. Tests: modules/test/test-fs-mv-cp-guard.js (12 asserts) — RED on HEAD (A1-A3, B1, B2, C2, E1), GREEN now; maintain --fix reports "node tests 7/7 pass". Browser on v4.6.104: "mv: /vtest: is a directory — refusing to overwrite it", child and source intact, "ls -l /vtest" lists /vtest (keep.txt), not the cwd.
* fix(newos4): rollback keeps scaffolding, checkpoints cannot collide, tests ↵Agando10 days1-7/+40
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | gate the build Review of the 08:30 implementer run (v4.6.102, checkpoint name fix). The shipped fix was correct; these are the defects it introduced or left behind, each measured first. - virtual-fs.js rollback(): the snapshot strips _snapshots/_sessions/.trash for bloat. Restoring it verbatim deleted every checkpoint, every agent session and the trash, made rollback irreversible and unrepeatable. Re-attach the live copies after the root swap. - checkpoint(): names are millisecond-resolution, so tight calls overwrote each other (measured 6 calls -> 3 snapshots). Dedupe with a -N suffix, refuse names containing "/" (written but unlistable), and prune by snapshot createdAt instead of filename sort, which let a custom name ("aaa-oldest") evict newer auto-named checkpoints. - module-predictor.js: legacy pre-v2 points have no `usable`, so a 0.079 s aborted run re-entered similarity as evidence and floored every prediction at 1 s. Backfill `usable` from the measurement on load, drop the ones it rejects, add a belt-and-braces guard in _findSimilar and count only usable points as actual executions. - newos4-maintain.py: run every node-runnable modules/test/test-*.js on each maintenance pass. The v4.6.102 regression was written but no job ever executed it. - newos4-steer.py: `done` now requires --commit <sha> that exists in the repo, and gains `reopen`. A written plan was being closed as done at 06:17 while the predictor fix shipped nowhere, so the implementer's pre-flight queue was empty. - steering.json: predictor item reopened, checkpoint item stamped with f7249ff. - maintenance board: MAINT-002/005/015 were fixed in v4.6.81-85 but still listed open/high; closed with their commit ids, and MAINT-010/016 annotated with the probes that keep them open. Tests: test-vfs-checkpoint 23/23 (RED 14/9 before), test-predictor-accuracy 38/38 (T1-T3 RED on HEAD), maintain --fix all PASS, browser round-trip "Checkpoint created: <name>" -> "Rolled back to checkpoint: <name>" on v4.6.103. Also carries the already-deployed 2026-10-26 syntax-gate hunk in newos4-maintain.py (syntax_failures + gated rebuild) that had never been committed.
* fix(newos4): print the checkpoint name actually saved (auto)Agando11 days1-4/+3
| | | | | | | | | | | | | | | | | | | | | | | | | | | Task 2 + ship of plan 2026-10-27_061724-newos4-checkpoint-name-mismatch.md. module-system.js `checkpoint` read the clock a second time for the printed name, so the name in "✅ Checkpoint created: <name>" differed from the name on disk by >=1ms whenever no explicit name was given. It now prints the value returned by fs.checkpoint(). try/catch and error strings unchanged. Bundles rebuilt (rebuild-bundles.py); LATEST_VER in module-loader.js and all three ?v= tags in main.html (core-bundle, module-loader, critical-ext-bundle) bumped v4.6.101 -> v4.6.102. Verification: $ node --check modules/core/module-system.js SYNTAX OK $ node --check modules/core/core-bundle.js BUNDLE SYNTAX OK $ node modules/test/test-vfs-checkpoint.js (x5) test-vfs-checkpoint: 10 passed, 0 failed $ curl -s https://webx11.duckdns.org/spaces/newos4/main.html | grep -o 'core-bundle.js?v=[^"]*' core-bundle.js?v=v4.6.102 $ python3 scripts/newos4-maintain.py PASS: version v4.6.102 x3 in main.html (expect 3) ... maintenance result: OK $ python3 scripts/newos4-skill-audit.py skill audit: 7/7 pass
* feat(feedback): review dashboard, public guide, terminal commands (v4.6.94)Agando11 days1-1/+158
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Dashboard (/admin/feedback.html, admin-gated) - User-feedback tab over the live :8908 API: list, filter, search, publish reply, archive/reopen. Reply is what the reporter sees in /stear -> Your reports & replies. - Agent-items tab: 3 plans parked at autonomy:review + 19 defect reports, one click each. - Snapshot age line + stale warning (>30 min), and the last apply outcome with failures. - Existing /admin/ tab relabelled "Client error log" (it renders feedback_log.json, not the feedback store) and now links to the review dashboard. Decision path - admin/api/feedback-decide.php: exact-match nginx location + auth_request, Origin check, item_id allowlist, append-only decisions.jsonl with fwrite+fsync verification. - scripts/newos4-feedback-apply.py (darkbot): last decision per item wins, superseded rows recorded, reject reverts safe->review, per-item try/except, ledger saved per mutation, realpath containment, exact standalone-line marker flip, file mode preserved, queue rotation at 1 MB. Steering directives now carry the report title. - scripts/newos4-feedback-collect.py: snapshot for the dashboard, 40-line plan head, steering shape assert (-1 = unknown), recent_apply outcomes. - newos4-feedback-sync.sh: flock -n so overlapping runs cannot double-apply. Guide + terminal - feedback-guide.html: which channel (terminal feedback / /stear / automatic reports), expectations, exact payload per channel, how to write a report that gets fixed. Linked from /stear, index.html footer and help.html. - newOS4: feedback list|read|reply|archive|reopen|stats|dashboard|help (+ feedback-admin alias), admin-token derived per call, argument-validated dispatch so a sentence like "feedback list is broken on mobile" is still a message, "feedback -- <text>" forces it. - help.html: 8 rows, coverage 362/362. Review: glm-5.3 REWORK -> 15 findings fixed, 5 refuted with evidence, 2 doc-only. Guide corrected (invented "60 MB per batch" replaced by the real 512 KB/5-per-10-min limits).
* feat(newos4): context window warns at 80% and says what trimming dropped ↵Agando14 days1-3/+26
| | | | | | | | | | | | | | | | | | (v4.6.91) P0-5 asked what to do about a 422k-token session against a 400k threshold. There is no 400k threshold in this tree — the budgets are maxContextTokens (12000) and maxContext*2 messages — so rather than chase a number that does not exist, this applies the decision that is defensible either way: warn before the cliff, never lose history without saying so. - 80% of the token budget prints one warning per crossing (no per-turn nagging) - the message-count trim unshifts a marker naming how many messages left the window and quoting the earliest one, instead of splicing them away silently - 'max-context-tokens (default: 2000)' now reports the real code default, 12000 Verified: 900/1000 tokens -> warns once, second call silent; 1500/1000 -> compresses to 17 messages with the summary head intact; 9-message window with maxContext=2 -> 4 messages, marker '[context trim: 6 earlier message(s) ... earliest was: "turn 0 ..."]'.
* fix(newos4): the virtual /sys entry leaked into listings, trees and counts ↵Agando14 days1-6/+22
| | | | | | | | | | | | | | | | (v4.6.88-90) One phantom entry, three symptoms: - ls() injected 'sys' whenever it was called without a path, so 'cd _agents; ls' advertised a sys directory that does not exist, and list_files('_agents/sys') answered 'Directory not found' — the reported false negative. Injection is now gated on dir === root. - tree() pushed the same virtual name into the walk and dereferenced it, throwing mid-output on every root listing. Guarded. - _gatherFileInfo() walked into /sys and counted a synthetic subtree: 146 files / 114 dirs for a 44-file / 15-dir store. Virtual entries are skipped. Verified: root still lists sys; _agents and skills do not; tree('') completes; world model total (44) equals an independent walk of fs.root (44), agree: true.
* fix(newos4): the audit stops inventing '999d unused' (v4.6.85)Agando2026-10-231-2/+5
| | | | | | | | | | | | getStaleSkills() returned daysUnused=999 for a skill that had useCount 0 and no lastUsed — a placeholder rendered by the audit as '999d unused', indistinguishable from a measurement. It told the operator to archive procedural memory that had simply never been instrumented. Now: daysUnused null + neverUsed true when there is no lastUsed; real day count otherwise; the audit prints '(never used)'. Verified: probe skill with no telemetry -> {daysUnused: null, neverUsed: true}; one last used 2026-08-01 -> 83; no 999 anywhere in getStaleSkills() or the audit output.
* fix(newos4): world_model_query default path parses again (v4.6.83-84)Agando2026-10-231-10/+36
| | | | | | | | | | | | | _modelDigest() returned JSON.stringify(...) cut at 4000 chars plus '…(digest truncated)'. The tool then did JSON.parse() on it, so the default detail threw 'Unexpected end of JSON input' every single call; detail:'full' worked only because it returned the object untouched. The digest now returns an OBJECT (_modelSnapshot) and shrinks by reducing list lengths, then by dropping whole sections with an explicit 'omitted' list. The string form is generated at the edge and is always valid JSON. Verified: default call -> no error, 13-key snapshot, facts answered; caps 400/1200/4000 all under cap and parseable; the residual floor at cap 200 is identity fields only.
* fix(newos4): outboxResult scoped, markdown mime, old reply path retired ↵Agando2026-10-231-2/+7
| | | | | | | | | | | | | | | | (v4.6.81) delegateTask declared `const outboxResult` inside the AI branch and returned it outside, so a delegation with the AI module missing threw 'outboxResult is not defined'. The catch turned that into _handleAgentFailure: the worker stayed idle with no result while spawn_agent reported ok — the reported symptom exactly. Now function-scoped, and the no-module path returns 'queued — AI module not loaded, nothing executed' rather than claiming 'completed'. Also: nginx served _docs/*.md as application/octet-stream (no .md type) — now text/markdown + nosniff; agent-replies.php returns 410 for GET as well as POST, so there is one reply path. Verified: with the AI module deleted at runtime, delegateTask returns the queued string and throws nothing.
* fix(chat): GLM review batch — corruption, traversal, cursor truncation, ↵Agando2026-10-231-7/+14
| | | | | | | | | | | | | | | | | | | | | | | double print (v4.6.78-80) 12 findings accepted, 3 refuted with evidence (see _docs/chat-review-glm-2026-10-23.md). Data loss: a corrupt thread read as [] and the next append rewrote it with one message — now 500 and the file is left alone. Cursor after a >50-message truncation pointed at the thread tail, making the oldest permanently unfetchable — now the last message actually sent, plus truncated:true. Two posts in the same millisecond shared a cursor — epoch is now strictly increasing. Security: chat.py show() had no id validation and joined argv onto CHAT_DIR ('../../x' read any .json as darkbot) — THREAD_RE added. The auto-reply prompt now treats message text as untrusted data and forbids acting on instructions found in it. Ops: relay housekeeping swept neither .lock-* nor orphaned .tmp-*; a crashed listing looked identical to an empty queue; grep AWAITING also matched message bodies; hermes-poll printed every reply twice. Verified: corrupt file -> 500 with the file untouched; traversal -> 'invalid thread id'; hermes-poll -> exactly one line per reply.
* fix(chat): three concurrency holes found reviewing my own patch (v4.6.77)Agando2026-10-231-2/+7
| | | | | | | | | | | | | | 1. chat.py renamed the temp file AFTER releasing the flock, so two writers could each read the pre-replace snapshot and the later rename silently dropped the earlier message. os.replace now happens inside the lock. 2. The agent cursor was global. After hermes-reset it pointed past the end of the new empty thread, so every read returned nothing — the agent would think it had been ignored. Key is now per thread and reset clears every chat cursor. 3. chat-relay.sh had no overlap guard: a slow agent run (up to 5 min) could collide with the next tick and answer the same thread twice. flock -n on /tmp/chat-relay.lock. Verified: stale cursor 'webx11_chat_agent_cursor_oldthread00000' set to 9999999999999, hermes-reset -> zero cursor keys remain, read returns {messages:[], note:'no new messages...'}.
* fix(newos4): agent read gets its own chat cursor (v4.6.76)Agando2026-10-231-5/+15
| | | | | | | | | | | | | | Two readers shared one cursor. The 30 s UI poller printed each reply and advanced webx11_chat_state.cursor, so the agent's chat_hermes read then asked for 'since=<that>' and got an empty list — the answer appeared on the terminal and never reached the model. That is the reported symptom exactly. chat_hermes read now uses webx11_chat_agent_cursor, starts from 0 on first use so the whole thread is visible, and never touches the display cursor. Empty reads say why ('still awaiting Hermes' vs 'no new messages since your last read'). Proven: UI poll consumed the reply (printed to terminal), then chat_hermes read still returned both the agent's question and Hermes's answer.
* feat(newos4): hermes command, auto-poll, chat_hermes tool, token reset (v4.6.73)Agando2026-10-231-26/+179
| | | | | | | | | | | | | | | | | | | Phase C as repo edits (browser edits in _mods/ do not survive a reload). - `hermes <msg>`: sends through the chat endpoint and always prints an outcome — '📨 Sending…' then either '✅ sent id=… epoch=…' or a coded error with a hint (429 retry later, 503 host busy, 401 run hermes-reset). `feedback` stays as the alias. - NOT named `chat`: ChatStudioModule already registers `chat` and dispatch walks modules in order, so a second `chat` was silently unreachable. Found by test, not by reading. - `hermes-poll` / `feedback-inbox`, `hermes-reset` (token rotation). - Poller: 30 s, paused while the tab is hidden, polls on visibilitychange, first poll adopts the cursor and prints nothing, cursor persisted in webx11_chat_state. - `chat_hermes` tool: send + read from inside the agent loop. - Contract/help updated for the rename. Acceptance: 'hermes acceptance test' in the browser, reply written host-side with scripts/chat.py, appeared in the terminal by itself 30 s later with no command typed (cursor 1792776973800).
* feat(newos4): on-demand worker labels and a server-canonical predictor store ↵Agando2026-10-231-6/+119
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | (v4.6.70) Two decisions recorded by the operator on 2026-10-23, implemented. 6 — spawn-on-demand is the design, so stop reporting it as a defect: - OrchestratorModule counts delegations inside delegateTask() - status prints "Mode: on-demand — N/M worker(s) available, X delegation(s) this session" and drops the "spawning is not dispatching" warning - per-agent lines read "available, no delegations yet (spawned ...)" / "N delegation(s), last ..." 10 — per-user, server file canonical, browser VFS a TTL'd read-through cache: - store = the user's own WebDAV space (<space>/_data/predictor.json), so identity comes from credentials the app already has; no new endpoint, no unauthenticated write surface - 5 min TTL: cache wins while fresh, server wins when it differs; a banner naming both counts prints only on divergence - write-through on save (debounced 5 s) with a MKCOL retry when the collection does not exist yet; store mode is printed by predictor-status - the cache write is guarded and verified — a missing /system/metrics made writeFile fail silently, which is how the first version lost a pull Verified live on v4.6.70 with a stubbed transport: pull applies 5 pts/5 paired and writes a stamped cache, the divergence banner fires once and not on the second (equal) pull, write-through PUT is issued, status reads "Store: server". A real WebDAV round-trip still needs the user's WebDAV config in-app; the agando space has no _data collection yet, so GET returns 404 -> "server (empty)".
* fix(newos4): grep, head and tail take every path argument (v4.6.67)Agando2026-10-231-32/+71
| | | | | | | | | | | | | | | | | | | | | | | Packet 1's multi-path ask was only honoured by wc in v4.6.58 — grep read only args[ai+1] and head/tail only positional[0], so `grep x a b` searched `a`, printed one file's hits, and exited 0. Same silent drop, three more commands. - grep: loops every path; multiple files print `file:line: text`, `-c` prints one count per file, missing files are reported per file and excluded from the summary count; single-file output unchanged - head: every path, `==> file <==` header when more than one; `head f 40` still works - tail: every path with headers; a numeric second positional is still the legacy `tail <file> <count>`; `-f` warns and follows only the first file instead of printing all of them and claiming otherwise Verified live on v4.6.67: grep "bug_id" a b -> a:2 + b:3 + "2 match(es) in 2 file(s)" grep -c "alpha" a b -> one count per file grep "alpha" a missing -> hits from a, per-file error, "in 1 file(s)" head -n 1 a b -> header + first line for each tail -n 1 a b -> header + last line for each tail a 2 -> legacy count still honoured tail -f a b -> warns, follows a only
* fix(newos4): an invalid grep pattern returns a message, not an exception ↵Agando2026-10-231-3/+18
| | | | | | | | | | | | | | | | | (v4.6.64) Packet 8. `new RegExp(pattern)` sat unguarded in the terminal grep handler and in VFS find()/grep(), so a bad pattern threw out of the command handler into the error ledger -- the same ledger packet 2 had to stop trusting. - module-fs.js grep: compile in try/catch, print "grep: invalid pattern: <pattern> (<engine message>)" as a warning and return - virtual-fs.js find()/grep(): same guard, return [] and stash the message on _lastPatternError instead of throwing into the module error wrapper Verified live on v4.6.64 with the packet's own repro: `grep "/\bug_id\" file` prints the message, the error counter stays 0 (was: one ledger entry per call), a valid pattern still matches, and fs.grep('[') returns [] instead of throwing.
* feat(newos4): agent status says never-scheduled vs waiting; `guides` command ↵Agando2026-10-231-4/+50
| | | | | | | | | | | | | | | | | | | | | | | | (v4.6.63) Packet 6 and 7. 6 — four agents showed `sleeping` and nothing distinguished "waiting for work" from "never handed a task". Added tasksAssigned/lastTaskAt to the agent record (counted where a task is actually assigned), and the status line now reads idle — NEVER SCHEDULED (0 tasks since 2026-10-23 16:22) idle — waiting for work (3 task(s), last 2026-10-23 16:22) parked — NEVER SCHEDULED ... plus "Dispatch: 0/3 agent(s) have ever been given a task" and a warning that spawning is not dispatching. 7 — /guides/ was reachable by nobody. New `guides` command lists it newest-first with sizes and the preview/cat hints; help.html gained a Guides nav entry and section with links to the published guides. Verified live on v4.6.63: `guides` → 2 entries after seeding two files; orchestrator-status shows 3x NEVER SCHEDULED on a fresh session (the packet's own observation), and "waiting for work (3 task(s) ...)" once a counter is set, with "Dispatch: 1/3".
* fix(newos4): tokenised body-aware skill search for load_skills (v4.6.62)Agando2026-10-231-7/+76
| | | | | | | | | | | | | | | | | | | | | | Packet 4. The matcher tested the whole query as one substring against name+description+tags: word order decided the outcome ("telemetry dashboard" = 0 while "telemetry" = 1), a sentence could never match, and skill bodies were never searched at all. - _skillTokens(): lowercase, split on non-word chars, drop <3-char words and a stopword list; a query made only of stopwords keeps its literal form - scoring: name 3, description 2, tags 2, body 1 + up to 1.0 for repeated hits, multiplied by token coverage (0.5 + 0.5 * matched/total), +3 phrase bonus so word order can never zero a real hit - results carry score and matchedTokens ("2/3"); top 3 by score, name as tiebreak - zero results now name the tokens searched and the closest skill names instead of "No matching skills found" Verified live on v4.6.62 via the real runTool entry point: "telemetry dashboard" and "dashboard telemetry" both return the same 2 skills (was 0 and 0); "how do I regenerate the skill index" returns skills-system-guide first (8.96, 2/3) where a sentence previously matched nothing; "info" returns 3 body matches; a nonsense query returns 0 with the tokens and closest names.
* fix(newos4): one error counter for errors/dashboard/world-model (v4.6.61)Agando2026-10-231-13/+64
| | | | | | | | | | | | | | | | | | | | | Packet 2 (three surfaces, three answers). Measured first: `errors` scanned /logs/current.log for [ERROR] lines, but that file only ever receives explicit terminal.log('ERROR', ...) calls -- exactly one call site in the whole app -- so it read 0. The dashboard added DebugSupervisor._errorDB plus failed tool calls (18), and the world model read the debug DB alone (11). - terminal.countErrors() / countLogErrors(): the single counter, exposed on the one object all three surfaces already hold - `errors` prints the canonical total with its breakdown, the entry list (hash, count, severity, message), and reports the log scan separately as a mirror - dashboard health.errors and health.errorBreakdown derive from it - world model telemetry.recentErrors derives from it, plus errorsBySource - terminal.log('ERROR', ...) now also records into DebugSupervisor, so the log is a human-readable mirror of one store instead of a second store almost nobody wrote Verified live on v4.6.61: with a seeded debug entry + failed tool call, all three report 2; a single log('ERROR') call moves debug 1->2 and the total by exactly 1 (the mirror line is counted once, in the log, and reported separately).
* fix(newos4): refuse shell composition, wc takes multiple paths (v4.6.58)Agando2026-10-231-14/+60
| | | | | | | | | | | | | | | | | | | Packet 1 and 3 (silent wrong answers from a shell-less terminal). - detectComposition() in the security gate refuses ; && || | before dispatch. `;` used to fail loudly, but `&&`/`||` folded the right-hand side into the left command's argv (wc -l f && grep -c x f returned "42 42"), and a `|` arriving from the AI run_command tool bypassed splitPipe entirely and degraded the pattern to empty -- which matches every line. Operators inside quotes are untouched. - wc accepts multiple paths: one line per file plus a total, per-file errors for missing paths. `wc -c a b` used to print only `a` and exit 0. - help.html: "There is no shell" callout at the top of the command reference, plus the v4.6.57/58 changelog entry. Verified live on v4.6.58 by replaying the exact call sequence used by both paths: interactive `wc -c a b` -> "14 a / 10 b / 24 total"; `&&` and `;` refused; `echo "a && b"` still echoes; AI run_command semantics with `|` and `&&` refused; single command and multi-file wc still return real output.
* fix(newos4): predictor arithmetic, deployed-source viewer, ls text output ↵Agando2026-10-231-25/+52
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | (v4.6.57) Acting on the "Request to the newOS4 team" packet (spaces/agando/guides/, re-verified by its author on 4.6.56). Three verified defects fixed: Packet 5 (predictor arithmetic) - module-predictor.js - the persisted field named mape held a SUM while predictor-status divided by the paired count: renamed to errorSum, older payloads read through - errorPct is stored raw; the 500% clamp is now render-only, so a 475x miss stays visible instead of being flattened to the ceiling - predictions are floored at 1 s and marked floored; a floored prediction is consumed but never scored as 100% error (a 0 s prediction is not a prediction) - observations shorter than 1.5 s (aborted/instant runs) are marked unusable and no longer train _findSimilar - one 0.079 s run was rounding the next prediction to 0 s - status prints an unpaired and an unscored count alongside the mean Packet 10 (verification cannot see its own fixes) - module-aiassist.js - view_module now returns the DEPLOYED source, prefixed with file and version, and only falls back to the _mods copy with a "may be stale" banner. The unversioned _mods/<Class>.js edit cache used to shadow the deployed file forever, so the viewer showed pre-fix code while the bundle was new. Proven by seeding a stale _mods/PredictorModule.js and calling the tool. Packet 1b (ls exits clean with no output) - module-fs.js - ls rendered only via printClickable(), which writes straight to the DOM, so any capture (AI run_command, diagnostics) saw an empty result and reported "empty directory". It now always prints a text line and keeps the clickable chips when nothing is capturing. Test: modules/test/test-predictor-accuracy.js 34/34 (new cases Q, R, S).
* fix(newos4): measure predictor accuracy over paired predictions (v4.6.56)Agando2026-10-231-95/+213
| | | | | | | | | | | | | | | | | | | | | | | | | The accuracy readout was structurally 0%: module-predictor.js:187 incremented the denominator on every observed run while the numerator only moved when a stored prediction existed, and predictions were stored only by the manual `predict` command (the AI tool predict_task computed one and discarded it). Replaying the AI loop's own call path with 6 tasks printed "Predictions made: 0, Accuracy: 0/6". - pending predictions with a 15m TTL, consumed by threaded id -> task hash -> cli-only similarity >= 0.5; unpaired runs never enter the denominator - duration scored within 30% or 3s (integer predictions vs fractional observations) - errorPct capped at 500% so one miss cannot poison the mean error - schema v2: v1 correct/total counters dropped on load, observed runs and the prediction counter kept; task text stored as an FNV-1a hash only - tuning (confidence-threshold, min-data-points, pair-ttl) persists - min-data-points now scales confidence (was declared, printed, settable, unread) - AI loop registers a prediction per turn and threads its id; predict_task returns predictionId; durations exclude chat-lock queue wait - status/dashboard say "n/a" instead of "N/A%", and carry paired count, mean error, turns error, pending and unpaired counts - test: modules/test/test-predictor-accuracy.js (26 assertions) Live on main.html v4.6.56: fresh session -> "Accuracy: n/a (0/0 paired)"; one paired run -> "100% (1/1 paired) - mean error 5% - turns +-0.0".
* fix(newos4): empty-pipeline crash, stacks in reports, frame in digest + ↵Agando2026-10-231-8/+37
| | | | | | | | | | | | | | | | | | | | | | | | | | dashboard (v4.6.55) Root cause: splitPipe() splits on every '|', so `help |`, `| wc -l`, `a || b` produced a stage with no argv and executePipeChain() called parts[0].toLowerCase() on undefined — 989 occurrences, first filed 2026-10-20, unfixable from the report because bug reports dropped the stack that named executePipeChain:3955. Same unguarded read existed in _classify(). - terminal.js: _classify() returns an inert classification for empty argv; executePipeChain() parses each stage once, filters stages with no argv (so the last real stage stays final), warns only when the whole pipeline is empty. Last-stage test now compares against the filtered length. - module-system.js: `bug` diagnostics include the stack (10 frames, 1200 chars); auto-filed reports carry it too — they are the majority of the inbox and were previously message-only. - report.php: per-stack 2 KB cap; fingerprint pinned to message/source/count so adding `stack` cannot break dedupe against reports filed before today. - ingest.php: first_frame() parsed once on ingest, stored as `frame` on each signature. - newos4-triage.py: FRAME| line with the first application frame. - dashboard.php: frame under each Top Errors row. - modules/test/test-pipe-guard.js: ordering tripwire (guard must precede the parts[0] read). Verified: test RED exit=1 -> GREEN exit=0; browser matrix on v4.6.55 (help |, | wc -l, echo hi || echo bye, ls | | wc -l, |) -> 0 new error signatures, "|" warns; auto-filed report now carries stack (RPT-...f3e31124, archived after the probe); frame = modules/core/core-bundle.js:3955:42; maintain OK; skill audit 7/7.
* fix(newos4): define VirtualFS.emptyTrash() — empty-trash threw since ↵Agando2026-10-231-0/+13
| | | | | | | | 2026-10-18 Report RPT-20261018-164646-89352963 ("this.fs.emptyTrash is not a function") was filed by the auto-filer and never fixed: terminal.js:1777 called a method VirtualFS never defined, so 'empty-trash --confirm' threw instead of emptying. Added emptyTrash() returning true only when it removed something. Verified live on v4.6.54: put -> rm -> empty-trash --confirm => 'Trash emptied.'; second run => 'Trash is already empty.'; .trash entries 0.
* fix(newos4-debug): idempotent ingest, admin-gated reads, honest counts ↵Agando2026-10-231-9/+98
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | (v4.6.52-53) Root cause: _syncToBackend() re-sent the whole error DB with cumulative counts every 300s, and ingest.php did count += client count per batch. 33 real signatures had accumulated 116,696 "occurrences" (one self-referential row alone: 46,133). Backend - ingest.php: per-(session,hash) synced map, adds only the delta, resets prev when the client count shrank (cleared localStorage), caps delta at 5000, bounds synced at 500 hashes and sessions at 200, normalises timestamps to +00:00, 30-day retention that also purges dropped hashes from every synced map, returns `accepted` (entries) next to `ingested` (occurrences). - stats.php/dashboard.php: total_errors = occurrences, unique_errors = signatures, last-ingest card. The two numbers were inconsistent by ~3,500x. - health.php: reports self_referential count and status=degraded instead of ok. - Pruned live data: 33 -> 23 records, 116,696 -> 58,759 occurrences, max cluster 110,226 -> 52,387 (archive kept in data/debug/archive/). - nginx: stats/errors/clusters/dashboard/agent-replies now behind auth_request /_wx11_admin_auth (302 without a session); ingest/report/health stay public for telemetry. Verified: reads 302, ingest 200, report 400. Client - module-debug-supervisor.js: sends only hashes whose count changed (persisted webx11_debug_synced), records synced counts only after success, stops auto-filing aborts/timeouts and debug-pipeline requests as bug reports (2 of the last 5 reports were "user aborted a request"). Verified with a scratch php -S copy: same batch POSTed 3x -> count stays 5; count=99999 -> clamped to 1000; retention dropped 10 legacy rows.
* fix(newos4): dashboard shows the real world model and stops dying after 10 s ↵Agando2026-10-221-11/+62
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | (v4.6.51) Two defects, both verified live before/after: 1. The World Model panel always said "Not synthesized yet". The page had a renderer for data.worldModel and _collectDashboardData() never set it -- the model was right there in WorldModelModule._model. Now collected and mapped to the renderer's exact keys (model has modules.loaded/skills.count/memory.nodes, the page reads modules/skills/memoryNodes), plus changesSinceLastSnapshot and the recent-error sample, with an honest "not synthesized" fallback. 2. The page discarded any payload older than 10 s and its own 5 s auto-refresh then showed "Cannot connect to newOS4". Stale payloads are now rendered with their age ("Data as of 1:20:14 AM - 3m ago"), labelled amber past 5 min. Also: the Refresh button wrote a localStorage key nobody read -> the terminal now listens for it and re-collects (page says "No terminal tab answered" when no terminal tab is open); storage events repaint instantly; the dead /api/dashboard fetch (404 on all three paths, 5 s stall) is gone; `dashboard --watch` / `--stop` re-collects every 5 s; header says the data is this browser's instance. Verified v4.6.51: panel rows "Synthesized 34s ago / v4.6.51 / 46-22 / 136-3 / 1 nodes 0 edges / OK / none recorded"; 130 s-old payload still renders with "- 3m ago" and no banner (previously "Cannot connect"); storage event moves the footer 13s -> 0s; watch timer advances the payload, --stop clears it; mobile 360/390 px all OK; 0 console errors.
* feat(newos4): world-model-ask answers with the LLM, grounded in the model ↵Agando2026-10-221-13/+118
| | | | | | | | | | | | | | | | | | | | | | | | | | (v4.6.50) `world-model-ask` was a 10-keyword lookup: any real question returned "Unknown query. Try: modules, skills, ...". The documented example ("what changed since yesterday") compared nothing — it restated the current snapshot. - _modelDigest(): whitelisted snapshot (measured 942-965 chars on fixtures, ~3.3 KB live) + recent error sample + changesSinceLastSnapshot - _buildAskPrompt(): "answer only from this JSON, never invent numbers, name the command that would produce missing data" - ask(): connects Ollama on demand, answers via chat(), falls back to the facts path with a visible warning; --facts keeps the instant lookup - _diffSincePrev(): keeps one previous snapshot in /system/world-model.prev.json so "what changed" is a real diff (files 136 -> 140) - world_model_query tool returns the snapshot (digest or detail:full) instead of ten canned strings; no nested LLM call inside a tool call - help text now says what it does; node test modules/test/test-world-model-ask.js (11 assertions, runs the real class) Verified live v4.6.50: facts "Loaded 46 modules" | LLM "The snapshot does not contain information regarding the cause of memory growth ... use world-model-refresh" | diff "files 136 -> 140" | tool snapshot 3,334 bytes.
* fix(newos4): coreutils flags + loop-guard arg key, both found by the agent's ↵Agando2026-10-221-24/+96
| | | | | | | | | | | | | | | | | | | | live run (v4.6.49) Agent session surfaced two real defects: 1. The A4 prompt told it to use grep -n / sed -n 'N,Mp' / wc -l / head -N — none of those flags parsed: grep treated '-n' as the pattern (6/6 variants failed), wc read '-l' as a filename, head looked for a file named '-2', sed had no range parser. Fixed the tools, not just the text: grep now accepts -n/-i/-c/-v/-l (line numbers were already printed), wc accepts -l/-w/-c, head accepts -N and -n N, sed accepts Np and N,Mp, tail no longer prints a blank line for a trailing newline. Prompt re-ranked to the working forms. 2. Loop guard keyed calls by tool name plus the raw arguments field. When arguments was already an object (parsed upstream), the key became 'run_command:[object Object]', collapsing distinct batched commands into one signature and producing false "called 3x" warnings. Now the key is JSON.stringify(arguments) whenever it is not a string (3 sites: sig, _sigR, _sigD). Verified live v4.6.49: grep -n/-c, wc -l, head -2, sed -n '2,3p', tail -n 1 all correct.
* fix(newos4): GLM review follow-ups for AI read/context (v4.6.47)Agando2026-10-211-23/+70
| | | | | | | | | | | | | - loop guard: a mutation unblocks the replay but no longer erases the repeat counter (identical mutating calls could loop forever) - tool traces are per agent tag, flushed in a catch as well as on success, ring-buffered (first 2 + last 10) so verification reads survive - read_file notes are metadata only (path/range/next_offset); no file bodies, no secrets, and web_search/web_fetch output is excluded from durable context (injection surface) - trace is role:'system' (was 'assistant' — the model re-read it as its own answer) - surrogate-safe pagination, 500 floor / 16000 cap, prompt limits aligned - prompt now names checks that exist in the in-page terminal (test, grep -n, wc -l) - compressed summary capped at 4000 chars; denied-destructive tool result carries tool_call_id
* feat(newos4): AI paged file reads, durable tool traces, 12k context budget, ↵Agando2026-10-211-8/+57
| | | | | | | | | | read/tool/verify prompt rules (v4.6.46) - read_file: offset/limit + JSON {total_chars,truncated,next_offset} (was a hard 8000-char cut with no resume) - tool results now leave a per-turn trace that is persisted into session context and survives compression - maxContextTokens 2000 -> 12000; compression keeps 8 exchanges and 400 chars/msg - system prompt: page big files, prefer grep -n / sed -n / head, re-read + verify after every edit - verified in the live browser: 30k file pages 0->8000->16000, prompt section present, v4.6.46 served
* fix(newos4): download link uses the user's space; WebDAV share copies ↵Agando2026-10-211-1/+9
| | | | | | | | | | | | verified file links; preview fullscreen+share; files->WebDAV upload button (v4.6.45) - download: URL built from _resolveSpaceName() (config url = logged-in user), not the page space - WebDAV browser 🔗: shares selected FILES only, URL-encoded, HEAD-verified (200) before claiming success; folders offer a tar fallback instead of a guaranteed-403 link - file manager preview: ⛶ fullscreen + 🔗 copy-link toolbar (was none) - file manager toolbar: ☁ WebDAV publishes selected/current file via webdav-push - modules/test/test-download-url.js: 4 PASS - rebuild bundles, bump v4.6.44 -> v4.6.45 (loader + 3 script tags)
* fix(newos4): stop agent accumulation on reload — stable role ids + boot ↵Agando2026-10-181-54/+143
| | | | | | | | | | reconciliation - spawnAgent() accepts an idHint and is idempotent; boot team uses agent-<role> - _reanimateFromVFS() adopts newest dir per default role, fs.rm()s duplicates/orphans - _bootAgents() awaits async VFS hydration before reconciling, then spawns only gaps - scripts/test-agents-reload.js: 5-boot regression + hydration-race test - v4.6.44 bundle rebuild + cache-bust
* newOS4 v4.6.40: review fixes (bug diagnostics, trash tree, dir paste, stale ↵Agando2026-10-181-16/+19
| | | | capture, preview)
* newOS4 v4.6.38: auto-file fatal errors with 3/day quota (A5)Agando2026-10-181-0/+52
|
* newOS4 v4.6.37: bug-report pipeline (report.php, bug command, HTML ↵Agando2026-10-181-0/+46
| | | | generator, telemetry-loop fix)
* newOS4 v4.6.34: running tool ledger in the prompt (D4)Agando2026-10-181-0/+16
|
* newOS4 v4.6.33: progress-based turn budget (D3)Agando2026-10-181-0/+22
|
* newOS4 v4.6.31: tool-result memo cache with epoch invalidation (D2)Agando2026-10-181-1/+35
|
* newOS4 v4.6.30: per-agent session isolation (D1)Agando2026-10-181-6/+18
|
* newOS4 v4.6.29: progress-aware loop guard + agent attributionAgando2026-10-181-22/+92
|
* newOS4: interactive AI course (terminal module, portal, atlas, telemetry, ↵Agando2026-10-181-2/+10
| | | | daily maintenance+cron) + free store card
* fix: sequential safe-tool execution — parallel fan-out corrupted ↵Agando2026-09-291-4/+7
| | | | print-capture attribution + shadow diff pairing
* newOS4 v4.6.15: reentrant print-capture stack, AI tail -f block, read-only ↵Agando2026-09-291-20/+42
| | | | sed in safeRead, security test harness (21 checks)