strom-research 1.19.0-beta.7 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.cs.md CHANGED
@@ -7,7 +7,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
7
7
 
8
8
  ## 1.19.0
9
9
 
10
- *Připravuje se: bety do 1.19.0-beta.7. Oddíl bude úplný s vydáním.*
10
+ *22. 10. 2026 · patří k aplikaci Strom 3.15.0.*
11
11
 
12
12
  - **Na archiv, který zmlkl, se čeká, místo opakovaných dotazů.** Když archiv přestane odpovídat, strom ho nechá na
13
13
  pokoji hodinu — a když týž den mlčí dál, dvě a pak čtyři hodiny — a agent se dozví, do kdy: mezitím dělá
@@ -16,7 +16,8 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
16
16
  - **První úklid staršího výzkumu se jednou nabídne.** Výzkum, u kterého se nastřádalo hodně dat dřív, než si v tom
17
17
  strom začal sám dělat pořádek (záznamy práce, náhledy snímků a podobně), dostane v menu jednu nabídku: co by se
18
18
  smazalo, kolik a proč — bez souhlasu nic. Po souhlasu se v tom pak udržuje pořádek samo; po odmítnutí menu a
19
- nastavení dál říkají, co jde uvolnit.
19
+ nastavení dál říkají, co jde uvolnit. `strom pack`, odebrání rodokmenu i menu uvádějí velikost balíku, zálohy nebo
20
+ snímků jako všude jinde (1,5 MB, u malých 24 kB), nikdy „0.0 MB“.
20
21
  - **Digitální knihovny napoprvé správně.** U Krameria Národní knihovny ČR a Moravské zemské knihovny a u ANNO
21
22
  Rakouské národní knihovny dostane agent jejich vlastní vyhledávací adresy — méně neúspěšných dotazů a hádání.
22
23
  - **Ostřejší část snímku se opravdu čte.** Když z archivu přišla část snímku v lepším rozlišení, agent i jeho čtenáři
@@ -43,8 +44,21 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
43
44
  agent nebo model (`strom fixes run <id> --agent <a> --model <m>`), s tím, kdo četl dřív; ten, kdo tu číst nemůže, se
44
45
  odmítne hned i s těmi, kdo mohou. Čas napravení se zaokrouhluje nahoru a po zkoušce se přepočítá podle čtení, jak
45
46
  běžela souběžně.
46
- - **Velikosti tak, jak je píše jazyk výzkumu.** `strom pack`, odebrání rodokmenu i menu uvádějí velikost balíku,
47
- zálohy nebo snímků jako všude jinde (1,5 MB, u malých 24 kB), nikdy „0.0 MB“.
47
+ - **Rodokmen, do kterého strom nesmí zapisovat, řekne proč.** strom zapíše do složky výzkumu pravidla pro Codex:
48
+ příkaz strom spuštěný samotný jde mimo sandbox Codexu — i na Windows, kde sandbox držel historii výzkumu zavřenou
49
+ a nic jiného nepustil — a co rozhoduje člověk a samotný git zůstává zakázané. Když zápisu do složky rodokmenu
50
+ sandbox agenta přesto brání, strom to řekne a agentovi poradí, co dělat podle toho, jak běží — spouštět strom
51
+ samotný, spustit Codex ve složce výzkumu, které důvěřuje, nebo nechat příkaz schválit mimo sandbox — místo holého
52
+ „chybí oprávnění“, které agent převyprávěl jako „nemám přístup k rodokmenu“. Kde agent nenechá žádnou svou značku,
53
+ pozná ho strom podle vlastní značky u agentů, které spouští, nebo podle programů, které ho spustily, a jako
54
+ pravděpodobnou příčinu jmenuje jeho sandbox, potom OneDrive nebo řízený přístup ke složkám ve Windows. Odmítnutý
55
+ zápis do historie se už nehlásí jako chybějící oprávnění k samotnému gitu a záloha nebo aktualizace, kterou nešlo
56
+ zapsat, žádá složku, do které lze zapisovat, a už neradí uvolnit místo na disku. `strom doctor` a `strom check`
57
+ zápis do výzkumu zkusí a řeknou, když nejde, i tam, kde složku nejde ani vypsat. Na disku bez pevných odkazů
58
+ (síťové úložiště, disk přesměrovaný přes Vzdálenou plochu) strom zase zapisuje, zámek jde jinou cestou, a zámek,
59
+ který pomalý disk ještě neukáže, už se po chvilce nepřebírá; rodokmen, který tam nejde založit, po sobě nic nenechá
60
+ a řekne to v jazyce výzkumu. Po instalaci přes npm na Windows spustí strom příkaz `strom.cmd` i tam, kde PowerShell
61
+ skripty odmítá.
48
62
 
49
63
  ## 1.18.0
50
64
 
package/CHANGELOG.de.md CHANGED
@@ -7,7 +7,7 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
7
7
 
8
8
  ## 1.19.0
9
9
 
10
- *In Vorbereitung: die Betas bis 1.19.0-beta.7. Der Abschnitt ist mit dem Release vollständig.*
10
+ *22.10.2026 · gehört zur Strom-App 3.15.0.*
11
11
 
12
12
  - **Auf ein verstummtes Archiv wird gewartet, statt es immer wieder zu fragen.** Antwortet ein Archiv nicht mehr,
13
13
  lässt strom es eine Stunde in Ruhe — zwei, dann vier, wenn es am selben Tag still bleibt — und der Agent erfährt, bis
@@ -16,8 +16,10 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
16
16
  immer wieder angefragt.
17
17
  - **Das erste Aufräumen einer älteren Forschung wird einmal angeboten.** Eine Forschung, neben der sich viel
18
18
  angesammelt hat, bevor strom dort selbst Ordnung hielt (Protokolle der Arbeitssitzungen, Ansichten von Scans und
19
- Ähnliches), bekommt im Menü ein Angebot: was ginge, wie viel und warum — ohne Ja geht nichts. Nach einem Ja wird dort
20
- von selbst Ordnung gehalten; nach einem Nein sagen Menü und Einstellungen weiter, was sich freigeben lässt.
19
+ Ähnliches), bekommt im Menü ein Angebot: was ginge, wie viel und warum — ohne Ja geht nichts. Nach einem Ja wird
20
+ dort von selbst Ordnung gehalten; nach einem Nein sagen Menü und Einstellungen weiter, was sich freigeben lässt.
21
+ `strom pack`, das Entfernen eines Stammbaums und das Menü nennen die Größe eines Pakets, einer Sicherung oder der
22
+ Bilder wie überall sonst (1,5 MB, bei kleinen 24 kB), nie „0.0 MB“.
21
23
  - **Digitale Bibliotheken beim ersten Versuch richtig durchsucht.** Für Kramerius der Tschechischen
22
24
  Nationalbibliothek und der Mährischen Landesbibliothek und für ANNO der Österreichischen Nationalbibliothek bekommt
23
25
  der Agent die eigenen Suchadressen der Bibliotheken — weniger fehlgeschlagene Anfragen und weniger Raten.
@@ -50,9 +52,24 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
50
52
  bereiter Agent oder ein anderes Modell (`strom fixes run <id> --agent <a> --model <m>`), mit dem, der vorher las;
51
53
  einer, der hier nicht lesen kann, wird sofort abgelehnt, mit denen, die es können. Die Zeit einer Behebung wird
52
54
  aufgerundet und nach dem Versuch nach den nebeneinander gelaufenen Lesungen neu gemessen.
53
- - **Größen, wie die Sprache der Forschung sie schreibt.** `strom pack`, das Entfernen eines Stammbaums und das Menü
54
- nennen die Größe eines Pakets, einer Sicherung oder der Bilder wie überall sonst (1,5 MB, bei kleinen 24 kB), nie
55
- „0.0 MB“.
55
+ - **Ein Stammbaum, in den strom nicht schreiben darf, sagt warum.** strom schreibt Regeln für Codex in den Ordner der
56
+ Forschung: ein allein ausgeführter strom-Befehl läuft außerhalb der Sandbox von Codex — auch unter Windows, wo die
57
+ Sandbox den Verlauf der Forschung verschlossen hielt und nichts anderes zuließ —, während das, was der Mensch
58
+ entscheidet, und git selbst verboten bleiben. Hindert die Sandbox eines Agenten strom trotzdem am Schreiben in den
59
+ Ordner des Stammbaums, sagt strom das und sagt dem Agenten, was zu tun ist, je nachdem, wie er läuft — strom allein
60
+ ausführen, Codex im Ordner der Forschung starten, dem er vertraut, oder den Befehl außerhalb der Sandbox genehmigen
61
+ lassen — statt eines bloßen „keine Berechtigung“, das der Agent als fehlenden Zugriff auf den Stammbaum wiedergab.
62
+ Wo ein Agent kein eigenes Merkmal hinterlässt, erkennt strom ihn an seinem eigenen Merkmal bei den Agenten, die es
63
+ startet, oder an den Programmen, die es gestartet haben, und nennt seine Sandbox als wahrscheinliche Ursache, danach
64
+ OneDrive oder den überwachten Ordnerzugriff von Windows. Ein vom Verlauf verweigertes Schreiben gilt nicht mehr als
65
+ fehlende Berechtigung für git selbst, und eine Sicherung oder ein Update, die nicht geschrieben werden konnten,
66
+ verlangen einen Ordner, in den geschrieben werden darf, statt Platz auf dem Datenträger freizumachen. `strom doctor`
67
+ und `strom check` versuchen, in die Forschung zu schreiben, und sagen, wenn es nicht geht, auch dort, wo sich der
68
+ Ordner nicht einmal auflisten lässt. Auf einem Laufwerk ohne feste Verknüpfungen (eine Netzwerkfreigabe, ein über
69
+ Remotedesktop umgeleitetes Laufwerk) schreibt strom wieder, seine Sperre geht einen anderen Weg, und eine Sperre, die
70
+ ein langsames Laufwerk noch nicht zeigt, wird nicht mehr nach einem Augenblick übernommen; ein Stammbaum, der sich
71
+ dort nicht anlegen lässt, hinterlässt nichts und sagt es in der Sprache der Forschung. Mit npm unter Windows
72
+ installiert, startet `strom.cmd` strom auch dort, wo PowerShell Skripte verweigert.
56
73
 
57
74
  ## 1.18.0
58
75
 
package/CHANGELOG.md CHANGED
@@ -7,16 +7,17 @@ The changes of each version in short. The full notes and the install lines are o
7
7
 
8
8
  ## 1.19.0
9
9
 
10
- *In preparation: the betas up to 1.19.0-beta.7. The section is complete with the release.*
10
+ *2026-10-22 · goes with the Strom app 3.15.0.*
11
11
 
12
12
  - **An archive that went silent is waited for, not asked again and again.** When an archive stops answering, strom
13
13
  leaves it alone for an hour — two, then four when it stays silent the same day — and the agent hears until when:
14
14
  it goes on with other work, lets a download in the background wait for the archive (up to 30 minutes) and go on by
15
15
  itself, or closes the session to go on later. No more asking the archive over and over meanwhile.
16
- - **The first tidy of an older research is offered once.** A research that gathered much beside it before strom
17
- kept order there by itself (logs of work sessions, views of scans and the like) gets one offer in the menu: what
18
- would go, how much and why, and nothing goes without a yes. After a yes it is kept in order by itself; after a no,
19
- the menu and the settings keep saying what can be freed.
16
+ - **The first tidy of an older research is offered once.** A research that gathered much beside it before strom kept
17
+ order there by itself (logs of work sessions, views of scans and the like) gets one offer in the menu: what would
18
+ go, how much and why, and nothing goes without a yes. After a yes it is kept in order by itself; after a no, the
19
+ menu and the settings keep saying what can be freed. `strom pack`, removing a family tree and the menu say how big a
20
+ package, a backup or the images are as everywhere else (1,5 MB in Czech, 24 kB for a small one), never "0.0 MB".
20
21
  - **Digital libraries searched right at the first try.** For Kramerius of the Czech National Library and of the
21
22
  Moravian Library and for ANNO of the Austrian National Library, the agent gets the libraries' own search
22
23
  addresses — fewer failed requests and less guessing.
@@ -46,8 +47,21 @@ The changes of each version in short. The full notes and the install lines are o
46
47
  ordering them. The remedy may be read by another agent or model ready here (`strom fixes run <id> --agent <a>
47
48
  --model <m>`), with which one read before said; one that cannot read here is refused at once, with those that can.
48
49
  The time of a remedy is rounded up and, after the trial, measured again by the readings as they ran side by side.
49
- - **Sizes as the research's language writes them.** `strom pack`, removing a family tree and the menu say how big a
50
- package, a backup or the images are as everywhere else (1,5 MB in Czech, 24 kB for a small one), never "0.0 MB".
50
+ - **A family tree strom may not write into says why.** strom writes Codex's rules into the research's folder: a strom
51
+ command run on its own goes outside Codex's sandbox — on Windows too, where the sandbox kept the research's history
52
+ closed and let nothing else run — while what the person decides and git itself stay forbidden. When an agent's
53
+ sandbox still keeps strom from writing into the family tree's folder, strom says so and tells the agent what to do as
54
+ it is there — run strom on its own, start Codex in the research's folder it trusts, or have the command approved
55
+ outside the sandbox — instead of a bare "no permission" the agent retold as no access to the family tree. Where an
56
+ agent leaves no mark of its own, strom knows it by its own mark on the agents it starts, or by the programs that
57
+ started it, and names its sandbox as the likely cause, then OneDrive or the controlled folder access of Windows. A
58
+ write the history refused is no longer said as missing rights to git itself, and a backup or an update that could not
59
+ be written asks for a folder with the right to write in it, no longer to free room on the disk. `strom doctor` and
60
+ `strom check` try writing into the research and say when it does not go, also where the folder cannot even be
61
+ listed. On a disk without hard links (a network share, a drive redirected over Remote Desktop) strom writes again,
62
+ its lock made another way, and a lock a slow disk does not show yet is no longer taken over after a moment; a family
63
+ tree that cannot be created there leaves nothing behind, said in the research language. Installed with npm on
64
+ Windows, `strom.cmd` runs strom where PowerShell refuses scripts.
51
65
 
52
66
  ## 1.18.0
53
67
 
package/README.md CHANGED
@@ -34,7 +34,10 @@ Nothing needs admin rights.
34
34
  - **macOS, Linux**:
35
35
  `curl -fsSL https://raw.githubusercontent.com/ACiDekCZ/strom-research/main/install/install.sh | sh`
36
36
 
37
- With Node 22.18 or newer: `npm install -g strom-research`.
37
+ With Node 22.18 or newer: `npm install -g strom-research`. On Windows the
38
+ installer above is the recommended way: installed with npm, PowerShell may
39
+ refuse `strom` ("running scripts is disabled on this system") — `strom.cmd`
40
+ runs it.
38
41
 
39
42
  The setup asks a few things — language, where to keep the research, which
40
43
  agent (it finds the installed ones and offers Claude if there is none), the
@@ -610,6 +610,20 @@
610
610
  "ui.doc.appurl.env.fix": "proměnnou STROM_APP_URL nastavit na adresu aplikace Strom ({web}, nebo její kopie na tomto počítači http://127.0.0.1:<port>/), nebo odebrat",
611
611
  "ui.doc.tree.ok": "{name}: data v pořádku a zapečetěná",
612
612
  "ui.doc.tree.bad": "{name}: {n, plural, one {# problém} few {# problémy} other {# problémů}}",
613
+ "ui.doc.write": "zápis do rodokmenu",
614
+ "ui.doc.write.ok": "do jeho .strom i .git lze zapisovat",
615
+ "ui.doc.write.sandbox": "{where}: zápis odepřen – strom běží pod agentem, nejspíš to zakazuje jeho sandbox",
616
+ "ui.doc.write.sandbox.fix": "spustit příkaz mimo sandbox agenta, nebo dát agentovi složku výzkumu jako zapisovatelnou",
617
+ "ui.doc.write.sandbox.fix.codex-strom": "spouštět strom samotný, bez roury a cd (pravidla .codex ve složce výzkumu ho pustí ze sandboxu Codexu; Codex je čte ve složce, které důvěřuje – znovu je zapíše strom agents sync), nebo schválit příkaz mimo sandbox Codexu (Codex se zeptá), nebo v nastavení zvolit úroveň „plná“ – volba jen člověka",
618
+ "ui.doc.write.sandbox.fix.codex": "spustit Codex ve složce výzkumu a důvěřovat jí (pravidla .codex v ní pustí strom ze sandboxu), nebo ho spustit přes strom („Pokračovat ve výzkumu s agentem“ v menu, nebo strom chat), nebo schválit příkaz mimo jeho sandbox",
619
+ "ui.doc.write.sandbox.fix.claude": "spustit Claude Code ve složce výzkumu (strom chat to tak dělá), nebo schválit příkaz mimo jeho sandbox",
620
+ "ui.doc.write.refused": "{where}: zápis odepřen – chybí právo zápisu",
621
+ "ui.doc.write.refused.fix": "složka, do které lze zapisovat",
622
+ "ui.doc.write.refused.fix.windows": "složka, do které lze zapisovat – zápisu může bránit i OneDrive nebo řízený přístup ke složkám v Zabezpečení Windows",
623
+ "ui.doc.write.refused.fix.agent": "strom spustil {agent}: nejspíš jeho sandbox – příkaz schválit mimo něj, nebo {agent} nechat pracovat na úrovni bez sandboxu; jinak složka, do které lze zapisovat",
624
+ "ui.doc.write.refused.fix.agent.windows": "strom spustil {agent}: nejspíš jeho sandbox – příkaz schválit mimo něj, nebo {agent} nechat pracovat na úrovni bez sandboxu; jinak složka, do které lze zapisovat – zápisu může bránit i OneDrive nebo řízený přístup ke složkám v Zabezpečení Windows",
625
+ "ui.doc.write.nolink": "{where}: disk neumí pevné odkazy – zámek jede náhradní cestou (pomalejší, když pracuje víc procesů naráz)",
626
+ "ui.doc.write.nolink.fix": "složka na vlastním disku tohoto počítače",
613
627
  "ui.doc.allok": "Všechno potřebné je na místě.",
614
628
  "ui.doc.problems": "{n, plural, one {# problém} few {# problémy} other {# problémů}} – co udělat, říkají šipky (strom setup nabídne instalaci toho, co chybí).",
615
629
  "ui.menu.fix": "Opravit to hned?",
@@ -1516,6 +1530,7 @@
1516
1530
  "ui.check.found": "{errors, plural, one {# chyba} few {# chyby} other {# chyb}}, {warnings} varování",
1517
1531
  "ui.check.unproven": "pozn.: osob, které nestojí na žádném vlastním záznamu: {n} → strom person list --unproven",
1518
1532
  "ui.check.counter": "počítadlo {prefix} zaostává za existujícími čísly (srovná se samo při příštím zápisu)",
1533
+ "ui.check.unwritable": "zápis sem je odepřen – proč, řekne strom doctor",
1519
1534
  "ui.export.file": "{file} ({for}) — {persons, plural, one {# osoba} few {# osoby} other {# osob}} · {families, plural, one {# rodina} few {# rodiny} other {# rodin}} · {facts, plural, one {# údaj} few {# údaje} other {# údajů}} · {sources, plural, one {# pramen} few {# prameny} other {# pramenů}}{skipped}",
1520
1535
  "ui.export.for.strom": "pro aplikaci Strom",
1521
1536
  "ui.export.for.strom.images": "pro aplikaci Strom, s obrázky",
@@ -2194,10 +2209,24 @@
2194
2209
  "ui.error.pack.missing.hint": "zadat ZIP soubor, který přišel (přetáhnout ho do terminálu)",
2195
2210
  "ui.error.pack.no-manifest": "v {path} není {manifest}: není to výzkum zabalený stromem",
2196
2211
  "ui.error.pack.no-manifest.hint": "zadat ZIP soubor, který přišel",
2197
- "ui.error.fs.no-permission": "chybí oprávnění{where}",
2212
+ "ui.error.fs.no-permission": "chybí oprávnění: {where}",
2198
2213
  "ui.error.fs.no-permission.hint": "vybrat složku, do které lze zapisovat",
2199
2214
  "ui.error.fs.held": "soubor {file} drží jiný program (antivirus, synchronizace nebo záloha, editor)",
2200
2215
  "ui.error.fs.held.hint": "za chvíli spustit tentýž příkaz znovu – nic z něj se neuložilo",
2216
+ "ui.error.fs.no-permission.windows.hint": "vybrat složku, do které lze zapisovat – na Windows může zápisu bránit i OneDrive nebo řízený přístup ke složkám v Zabezpečení Windows",
2217
+ "ui.error.fs.no-permission.agent.hint": "strom spustil {agent} a zápis nejspíš zakazuje jeho sandbox: příkaz schválit mimo sandbox, nebo {agent} nechat pracovat na úrovni bez sandboxu; jinak vybrat složku, do které lze zapisovat",
2218
+ "ui.error.fs.no-permission.agent.windows.hint": "strom spustil {agent} a zápis nejspíš zakazuje jeho sandbox: příkaz schválit mimo sandbox, nebo {agent} nechat pracovat na úrovni bez sandboxu; jinak vybrat složku, do které lze zapisovat – na Windows může zápisu bránit i OneDrive nebo řízený přístup ke složkám v Zabezpečení Windows",
2219
+ "ui.error.fs.no-permission.unseen.hint": "strom nevidí programy, které ho spustily, jako v sandboxu agenta: když strom spustil agent, zápis nejspíš zakazuje jeho sandbox – příkaz schválit mimo sandbox; jinak vybrat složku, do které lze zapisovat",
2220
+ "ui.error.fs.no-permission.unseen.windows.hint": "strom nevidí programy, které ho spustily, jako v sandboxu agenta: když strom spustil agent, zápis nejspíš zakazuje jeho sandbox – příkaz schválit mimo sandbox; jinak vybrat složku, do které lze zapisovat – na Windows může zápisu bránit i OneDrive nebo řízený přístup ke složkám v Zabezpečení Windows",
2221
+ "ui.error.fs.sandboxed": "chybí oprávnění: {where} – strom běží pod agentem a nejspíš to zakazuje jeho sandbox",
2222
+ "ui.error.fs.sandboxed.hint": "spustit příkaz mimo sandbox agenta, nebo pracovat ve složce výzkumu (i s jejími .git a .strom zapisovatelnými)",
2223
+ "ui.error.git.unwritable": "git nemůže zapisovat do výzkumu: {where}",
2224
+ "ui.error.git.unwritable.hint": "vybrat složku, do které lze zapisovat",
2225
+ "ui.error.fs.folder": "místo souboru je zadaná složka: {path}",
2226
+ "ui.error.init.unwritable": "rodokmen nejde založit v {where}: zápis tam je odepřen – nic z něj tam nezůstalo",
2227
+ "ui.error.init.unwritable.hint": "vybrat složku, do které lze zapisovat",
2228
+ "ui.error.init.failed": "rodokmen nejde založit v {where}: {detail} – nic z něj tam nezůstalo",
2229
+ "ui.error.init.failed.hint": "složka na vlastním disku tohoto počítače (síťový nebo přesměrovaný disk může zápis nebo historii odmítnout)",
2201
2230
  "ui.task.none": "Žádné úkoly.",
2202
2231
  "ui.research.none": "Žádné linie výzkumu.",
2203
2232
  "ui.person.none": "Žádné osoby.",
@@ -2270,6 +2299,10 @@
2270
2299
  "ui.error.backup.failed.hint": "Uvolnit místo na disku ({where}) a spustit strom znovu – nebo se vrátit: strom update --channel {back}",
2271
2300
  "ui.error.backup.failed.update": "Zálohu před přechodem z {from} na {to} se nepodařilo vytvořit: {detail}. Nic se nenainstalovalo.",
2272
2301
  "ui.error.backup.failed.update.hint": "Uvolnit místo na disku ({where}) a znovu strom update --channel {channel}",
2302
+ "ui.error.backup.failed.refused.hint": "Zápis do {where} je odepřen: vybrat složku, do které lze zapisovat, pak spustit strom znovu – nebo se vrátit: strom update --channel {back}",
2303
+ "ui.error.backup.failed.other.hint": "Spustit strom znovu (víc řekne strom doctor) – nebo se vrátit: strom update --channel {back}",
2304
+ "ui.error.backup.failed.update.refused.hint": "Zápis do {where} je odepřen: vybrat složku, do které lze zapisovat, pak znovu strom update --channel {channel}",
2305
+ "ui.error.backup.failed.update.other.hint": "Znovu strom update --channel {channel} (víc řekne strom doctor)",
2273
2306
  "ui.error.folder.in-program": "{folder} leží ve složce programu strom {program} – aktualizace ji nahrazuje a odinstalace odstraňuje, výzkum by šel s ní. Nic se nezměnilo.",
2274
2307
  "ui.error.folder.in-program.hint": "Zvolit složku mimo ni, například {suggested}",
2275
2308
  "ui.error.folder.in-program.default.hint": "Zadat složku mimo ni: strom setup --home \"{suggested}\" --yes",
@@ -610,6 +610,20 @@
610
610
  "ui.doc.appurl.env.fix": "die Variable STROM_APP_URL auf eine Adresse der Strom-App setzen ({web}, oder eine Kopie auf diesem Computer http://127.0.0.1:<port>/) oder entfernen",
611
611
  "ui.doc.tree.ok": "{name}: Daten in Ordnung und versiegelt",
612
612
  "ui.doc.tree.bad": "{name}: {n, plural, one {# Problem} other {# Probleme}}",
613
+ "ui.doc.write": "Schreiben in diesen Stammbaum",
614
+ "ui.doc.write.ok": "in sein .strom und .git kann geschrieben werden",
615
+ "ui.doc.write.sandbox": "{where}: Schreiben verweigert – strom läuft unter einem Agenten, wahrscheinlich verbietet es dessen Sandbox",
616
+ "ui.doc.write.sandbox.fix": "den Befehl außerhalb der Sandbox des Agenten ausführen, oder dem Agenten den Ordner der Forschung als beschreibbar geben",
617
+ "ui.doc.write.sandbox.fix.codex-strom": "strom allein ausführen, ohne Pipe und cd (die Regeln in .codex im Ordner der Forschung lassen es aus der Sandbox von Codex; Codex liest sie in einem Ordner, dem es vertraut – strom agents sync schreibt sie neu), oder den Befehl außerhalb der Sandbox von Codex genehmigen (Codex fragt), oder in den Einstellungen die Stufe „voll“ wählen – allein die Wahl des Menschen",
618
+ "ui.doc.write.sandbox.fix.codex": "Codex im Ordner der Forschung starten und ihm vertrauen (die Regeln in .codex lassen strom aus der Sandbox), oder über strom starten („Die Forschung mit dem Agenten fortsetzen“ im Menü, oder strom chat), oder den Befehl außerhalb seiner Sandbox genehmigen",
619
+ "ui.doc.write.sandbox.fix.claude": "Claude Code im Ordner der Forschung starten (strom chat tut das), oder den Befehl außerhalb seiner Sandbox genehmigen",
620
+ "ui.doc.write.refused": "{where}: Schreiben verweigert – keine Schreibberechtigung",
621
+ "ui.doc.write.refused.fix": "ein Ordner, in den geschrieben werden darf",
622
+ "ui.doc.write.refused.fix.windows": "ein Ordner, in den geschrieben werden darf – auch OneDrive oder der überwachte Ordnerzugriff der Windows-Sicherheit kann das Schreiben verhindern",
623
+ "ui.doc.write.refused.fix.agent": "strom wurde von {agent} gestartet: vermutlich dessen Sandbox – den Befehl außerhalb genehmigen oder {agent} auf einer Stufe ohne Sandbox arbeiten lassen; sonst ein Ordner, in den geschrieben werden darf",
624
+ "ui.doc.write.refused.fix.agent.windows": "strom wurde von {agent} gestartet: vermutlich dessen Sandbox – den Befehl außerhalb genehmigen oder {agent} auf einer Stufe ohne Sandbox arbeiten lassen; sonst ein Ordner, in den geschrieben werden darf – auch OneDrive oder der überwachte Ordnerzugriff der Windows-Sicherheit kann das Schreiben verhindern",
625
+ "ui.doc.write.nolink": "{where}: das Laufwerk kann keine festen Verknüpfungen – die Sperre geht einen anderen Weg (langsamer, wenn mehrere zugleich arbeiten)",
626
+ "ui.doc.write.nolink.fix": "ein Ordner auf der eigenen Festplatte dieses Computers",
613
627
  "ui.doc.allok": "Alles Nötige ist vorhanden.",
614
628
  "ui.doc.problems": "{n, plural, one {# Problem} other {# Probleme}} – den Pfeilen folgen (strom setup bietet an, Fehlendes zu installieren).",
615
629
  "ui.menu.fix": "Jetzt beheben?",
@@ -1516,6 +1530,7 @@
1516
1530
  "ui.check.found": "{errors} Fehler, {warnings, plural, one {# Warnung} other {# Warnungen}}",
1517
1531
  "ui.check.unproven": "Hinweis: Personen, die sich auf keinen eigenen Eintrag stützen: {n} → strom person list --unproven",
1518
1532
  "ui.check.counter": "Zähler {prefix} liegt hinter den vorhandenen Nummern (gleicht sich beim nächsten Schreiben selbst aus)",
1533
+ "ui.check.unwritable": "Schreiben hierher verweigert – warum, sagt strom doctor",
1519
1534
  "ui.export.file": "{file} ({for}) — {persons, plural, one {# Person} other {# Personen}} · {families, plural, one {# Familie} other {# Familien}} · {facts, plural, one {# Angabe} other {# Angaben}} · {sources, plural, one {# Quelle} other {# Quellen}}{skipped}",
1520
1535
  "ui.export.for.strom": "für die Strom-App",
1521
1536
  "ui.export.for.strom.images": "für die Strom-App, mit Bildern",
@@ -2194,10 +2209,24 @@
2194
2209
  "ui.error.pack.missing.hint": "die ZIP-Datei angeben, die gekommen ist (ins Terminal ziehen)",
2195
2210
  "ui.error.pack.no-manifest": "keine {manifest} in {path}: keine von strom gepackte Forschung",
2196
2211
  "ui.error.pack.no-manifest.hint": "die ZIP-Datei angeben, die gekommen ist",
2197
- "ui.error.fs.no-permission": "keine Berechtigung{where}",
2212
+ "ui.error.fs.no-permission": "keine Berechtigung: {where}",
2198
2213
  "ui.error.fs.no-permission.hint": "einen Ordner wählen, in den geschrieben werden darf",
2199
2214
  "ui.error.fs.held": "ein anderes Programm hält {file} (Virenschutz, Synchronisation oder Sicherung, Editor)",
2200
2215
  "ui.error.fs.held.hint": "denselben Befehl gleich noch einmal ausführen – nichts davon wurde gespeichert",
2216
+ "ui.error.fs.no-permission.windows.hint": "einen Ordner wählen, in den geschrieben werden darf – unter Windows kann auch OneDrive oder der überwachte Ordnerzugriff der Windows-Sicherheit das Schreiben verhindern",
2217
+ "ui.error.fs.no-permission.agent.hint": "strom wurde von {agent} gestartet, und vermutlich verbietet dessen Sandbox das Schreiben: den Befehl außerhalb der Sandbox genehmigen oder {agent} auf einer Stufe ohne Sandbox arbeiten lassen; sonst einen Ordner wählen, in den geschrieben werden darf",
2218
+ "ui.error.fs.no-permission.agent.windows.hint": "strom wurde von {agent} gestartet, und vermutlich verbietet dessen Sandbox das Schreiben: den Befehl außerhalb der Sandbox genehmigen oder {agent} auf einer Stufe ohne Sandbox arbeiten lassen; sonst einen Ordner wählen, in den geschrieben werden darf – unter Windows kann auch OneDrive oder der überwachte Ordnerzugriff der Windows-Sicherheit das Schreiben verhindern",
2219
+ "ui.error.fs.no-permission.unseen.hint": "strom sieht die Programme nicht, die es gestartet haben, wie in der Sandbox eines Agenten: wurde strom von einem Agenten gestartet, verbietet vermutlich dessen Sandbox das Schreiben – den Befehl außerhalb der Sandbox genehmigen; sonst einen Ordner wählen, in den geschrieben werden darf",
2220
+ "ui.error.fs.no-permission.unseen.windows.hint": "strom sieht die Programme nicht, die es gestartet haben, wie in der Sandbox eines Agenten: wurde strom von einem Agenten gestartet, verbietet vermutlich dessen Sandbox das Schreiben – den Befehl außerhalb der Sandbox genehmigen; sonst einen Ordner wählen, in den geschrieben werden darf – unter Windows kann auch OneDrive oder der überwachte Ordnerzugriff der Windows-Sicherheit das Schreiben verhindern",
2221
+ "ui.error.fs.sandboxed": "keine Berechtigung: {where} – strom läuft unter einem Agenten, und wahrscheinlich verbietet es dessen Sandbox",
2222
+ "ui.error.fs.sandboxed.hint": "den Befehl außerhalb der Sandbox des Agenten ausführen, oder im Ordner der Forschung arbeiten (auch dessen .git und .strom beschreibbar)",
2223
+ "ui.error.git.unwritable": "git kann nicht in die Forschung schreiben: {where}",
2224
+ "ui.error.git.unwritable.hint": "einen Ordner wählen, in den geschrieben werden darf",
2225
+ "ui.error.fs.folder": "statt einer Datei wurde ein Ordner angegeben: {path}",
2226
+ "ui.error.init.unwritable": "der Stammbaum lässt sich in {where} nicht anlegen: das Schreiben dort wird verweigert – nichts davon ist dort geblieben",
2227
+ "ui.error.init.unwritable.hint": "einen Ordner wählen, in den geschrieben werden darf",
2228
+ "ui.error.init.failed": "der Stammbaum lässt sich in {where} nicht anlegen: {detail} – nichts davon ist dort geblieben",
2229
+ "ui.error.init.failed.hint": "ein Ordner auf der eigenen Festplatte dieses Computers (ein Netz- oder umgeleitetes Laufwerk kann das Schreiben oder den Verlauf verweigern)",
2201
2230
  "ui.task.none": "Keine Aufgaben.",
2202
2231
  "ui.research.none": "Keine Forschungsrichtungen.",
2203
2232
  "ui.person.none": "Keine Personen.",
@@ -2270,6 +2299,10 @@
2270
2299
  "ui.error.backup.failed.hint": "Platz auf dem Datenträger freimachen ({where}) und strom erneut starten – oder zurück: strom update --channel {back}",
2271
2300
  "ui.error.backup.failed.update": "Die Sicherung vor dem Wechsel von {from} auf {to} ließ sich nicht erstellen: {detail}. Es wurde nichts installiert.",
2272
2301
  "ui.error.backup.failed.update.hint": "Platz auf dem Datenträger freimachen ({where}), dann erneut strom update --channel {channel}",
2302
+ "ui.error.backup.failed.refused.hint": "Das Schreiben in {where} wird verweigert: einen Ordner wählen, in den geschrieben werden darf, dann strom erneut starten – oder zurück: strom update --channel {back}",
2303
+ "ui.error.backup.failed.other.hint": "strom erneut starten (strom doctor sagt mehr) – oder zurück: strom update --channel {back}",
2304
+ "ui.error.backup.failed.update.refused.hint": "Das Schreiben in {where} wird verweigert: einen Ordner wählen, in den geschrieben werden darf, dann erneut strom update --channel {channel}",
2305
+ "ui.error.backup.failed.update.other.hint": "Erneut strom update --channel {channel} (strom doctor sagt mehr)",
2273
2306
  "ui.error.folder.in-program": "{folder} liegt im Programmordner von strom {program} – ein Update von strom ersetzt, was dort liegt, und die Deinstallation entfernt es, die Forschung mit. Nichts wurde geändert.",
2274
2307
  "ui.error.folder.in-program.hint": "Einen Ordner außerhalb wählen, zum Beispiel {suggested}",
2275
2308
  "ui.error.folder.in-program.default.hint": "Einen Ordner außerhalb angeben: strom setup --home \"{suggested}\" --yes",
@@ -13,7 +13,7 @@ import { PROFILES, selfReading } from "./profiles.js";
13
13
  import { isArchive } from "../core/mode.js";
14
14
  import { Settings } from "../core/config.js";
15
15
  import { agentAppUrl, appSite } from "../core/stromapp.js";
16
- import { configDir } from "../core/paths.js";
16
+ import { configDir, ownCommand } from "../core/paths.js";
17
17
  import { within } from "../core/relocate.js";
18
18
  import { agentBrowser } from "../core/connector.js";
19
19
  import { CHROME_ALLOW, CHROME_DENY, chromeDomain } from "../core/browser.js";
@@ -50,16 +50,20 @@ the researcher; \`strom\` is your only way to read and change the research.
50
50
  5. Material from the user goes in with \`strom intake <file or folder>\` or
51
51
  \`strom intake --text "…"\`. Exit code 3 means strom needs an answer from the
52
52
  user; exit code 4 means the USER must run the given command in their own
53
- terminal — never do it yourself.
53
+ terminal — never do it yourself. strom saying your sandbox refused writing
54
+ (\`fs.sandboxed\`): never another folder or setting — ask for the command
55
+ to run outside your sandbox, or work in this folder (its \`.git\` and
56
+ \`.strom\` too).
54
57
  6. You may read \`inputs/\`, \`output/\`, \`notes/\` and \`.strom/views/\`, and write
55
58
  your own working notes in \`notes/\`. Scans are looked at through views:
56
59
  \`strom media view B0001:57 --half left\` writes one to \`.strom/views/\`;
57
60
  the views of a scan or a batch come in one call (\`B0001:57-60 --half both\`).
58
61
  A text in those files: \`strom grep "<text>"\` (each line with its file and
59
62
  number) — never read a big file in pieces.
60
- 7. Run strom commands on their own — no pipes (\`| head\`, \`| grep\`): output is
61
- already short, listings take \`--limit\` and \`--page\`, and piped commands may
62
- be refused by your permissions.${ps ? `\n ${ps}` : ""}
63
+ 7. Run strom commands on their own — no pipes (\`| head\`, \`| grep\`), no
64
+ redirection (\`2>&1\`), no \`cd\` before them: output is already short,
65
+ listings take \`--limit\` and \`--page\`, and such commands may be refused by
66
+ your permissions (Codex's sandbox lets strom out only on its own).${ps ? `\n ${ps}` : ""}
63
67
  8. Other agents may work on this tree too (Claude Code, Codex, Antigravity,
64
68
  OpenCode, Grok — the user's choice). \`strom\` shows who is working now; never take a task
65
69
  another one has started.
@@ -257,6 +261,7 @@ function treeRules(tree, agent) {
257
261
  ...shell("git:*"),
258
262
  // A password is typed by the user in their own terminal, and so is installing a plugin; the seal is strom's.
259
263
  // (Consents the agent may ask for — strom allow …: strom asks the person in a window.)
264
+ // A rule matches only the command right after strom's name: strom itself refuses these to an agent (CommandDef.person).
260
265
  ...shell("strom login:*"),
261
266
  ...shell("strom connector add:*"),
262
267
  ...shell("strom connector remove:*"),
@@ -309,13 +314,41 @@ CLAUDE.md in this folder is written for Claude Code: its way of delegating (the
309
314
  Agent tool, the models opus, sonnet and haiku) is not yours. Follow AGENTS.md,
310
315
  and read scans yourself as it says under "Reading scans".
311
316
  `;
317
+ /**
318
+ * Codex's rules for this tree (.codex/rules/strom.rules, read once the folder is trusted): strom run as a command of
319
+ * its own goes outside Codex's sandbox without asking — it writes the research's .strom and .git, which the sandbox
320
+ * keeps closed (measured 2026-10-22, codex-cli 0.163: on Windows .git stays closed even as a writable root, and nothing
321
+ * runs at all without a rule; on macOS too) — what the person decides stays forbidden, and git itself. Only strom's
322
+ * command alone: one in a pipe or after `cd` runs in the sandbox (Codex's rules match a plain command). Codex keeps
323
+ * .codex closed to the agent itself.
324
+ * The whole of strom is allowed, as for Claude Code (Bash(strom:*)): what strom writes outside the research (strom
325
+ * export --out, strom init <folder>) is strom's own decision, and what is the person's it refuses to an agent itself.
326
+ * The forbidden rules match only the command right after strom's name (strom --json seal adopt is not matched — a
327
+ * value-taking option cannot be listed): they are the second layer and give the agent the reason; the first is strom
328
+ * refusing the user's own commands to any agent, whatever comes before them (CommandDef.person).
329
+ */
330
+ export function codexRules(tree) {
331
+ const own = ownCommand(tree.env);
332
+ const names = JSON.stringify(["strom", "strom.cmd", ...(own ? [own, `${own}.cmd`] : [])]);
333
+ const rule = (pattern, decision, why) => `prefix_rule(pattern = [${pattern}], decision = "${decision}", justification = ${JSON.stringify(why)})`;
334
+ return [
335
+ "# strom: generated (strom agents sync) — Codex's rules for this family tree; changed by strom only",
336
+ rule(names, "allow", "strom writes the research (its .strom and .git) and keeps its rules"),
337
+ rule(`${names}, ["login", "seal"]`, "forbidden", "a login and the seal are the user's own"),
338
+ rule(`${names}, "connector", ["add", "remove"]`, "forbidden", "installing a plugin is the user's own"),
339
+ rule('"git"', "forbidden", "strom keeps the history: never git itself"),
340
+ "",
341
+ ].join("\n");
342
+ }
343
+ export const CODEX_RULES = path.join(".codex", "rules", "strom.rules");
312
344
  /** Denied as well when the user lets the agent do everything else (agent.permissions full). */
313
345
  export const BYPASS_DENY = [
314
346
  // its own permissions and instructions
315
347
  "Edit(.claude/**)",
316
348
  ...["AGENTS.md", "CLAUDE.md"].map((f) => `Edit(${f})`),
317
- // (Grok Build's own rules and instructions)
349
+ // (Grok Build's and Codex's own rules and instructions)
318
350
  "Edit(.grok/**)",
351
+ "Edit(.codex/**)",
319
352
  // downloads round strom's limiter
320
353
  "Bash(curl:*)",
321
354
  "Bash(wget:*)",
@@ -459,6 +492,7 @@ function dropAgentFiles(tree) {
459
492
  [path.join(".grok", "config.toml"), grokConfig(tree)],
460
493
  [path.join(".grok", "rules", "strom.md"), GROK_RULES],
461
494
  [GROK_HOOKS, JSON.stringify(grokWebHooks(tree.root), null, 2) + "\n"],
495
+ [CODEX_RULES, codexRules(tree)],
462
496
  ];
463
497
  for (const [rel, content] of generated) {
464
498
  const file = path.join(tree.root, rel);
@@ -505,7 +539,7 @@ function dropAgentFiles(tree) {
505
539
  export const GROK_HOOKS = path.join(".grok", "hooks", "strom.json");
506
540
  /** Claude Code's subagent for reading scans, in the tree. */
507
541
  export const CLAUDE_SCAN_READER = path.join(".claude", "agents", `${SCAN_READER}.md`);
508
- export const AGENT_FILES = ["AGENTS.md", "CLAUDE.md", path.join(".claude", "settings.json"), CLAUDE_SCAN_READER, "opencode.json", path.join(".grok", "config.toml"), path.join(".grok", "rules", "strom.md"), GROK_HOOKS];
542
+ export const AGENT_FILES = ["AGENTS.md", "CLAUDE.md", path.join(".claude", "settings.json"), CLAUDE_SCAN_READER, "opencode.json", path.join(".grok", "config.toml"), path.join(".grok", "rules", "strom.md"), GROK_HOOKS, CODEX_RULES];
509
543
  /**
510
544
  * strom's files of the agents written for the tree (only what changed: the files written, to commit). `said.shim`: the
511
545
  * shim in .strom/bin was written now (never committed: not among the files).
@@ -540,6 +574,7 @@ export function syncAgentFiles(tree, said = {}) {
540
574
  write(path.join(".grok", "config.toml"), grokConfig(tree));
541
575
  write(path.join(".grok", "rules", "strom.md"), GROK_RULES);
542
576
  write(GROK_HOOKS, JSON.stringify(grokWebHooks(tree.root), null, 2) + "\n");
577
+ write(CODEX_RULES, codexRules(tree));
543
578
  // the strom the agent hooks name (the tree's .strom/bin, never committed) — there also for a conversation strom did not start
544
579
  if (!tree.dryRun) {
545
580
  try {
@@ -56,7 +56,9 @@ mentions strom, a family tree or the Strom app.
56
56
  "Continue the research with the agent".
57
57
  6. When strom needs the user's consent it opens a window on their screen —
58
58
  tell them to answer it; you cannot. Exit code 4 means the user must run
59
- the given command themselves.
59
+ the given command themselves. strom saying your sandbox refused writing
60
+ (\`fs.sandboxed\`): never another folder or setting — ask for the command
61
+ to run outside your sandbox, or work in the family tree's folder.
60
62
  7. Old handwriting is read by your strongest model — never a faster or
61
63
  cheaper model, subagent or pass; a name or place the next search depends
62
64
  on is read again on an enlarged crop before it is recorded.
@@ -17,7 +17,9 @@
17
17
  //
18
18
  // Claude Code always gets the tree's allow and deny lists (--settings); Codex
19
19
  // works in its sandbox on the tree folder with its .git (strom commits) and the
20
- // shared folder writable and the network on (strom fetches).
20
+ // shared folder writable and the network on (strom fetches) — and strom itself
21
+ // runs outside it by the tree's rules (.codex/rules/strom.rules): on Windows its
22
+ // sandbox keeps .git closed though it is named writable.
21
23
  import { claudeArgs } from "../runners/claude.js";
22
24
  import { codexTrust, codexWritable } from "../runners/codex.js";
23
25
  import { grokOwnEnv, grokTrust } from "../runners/grok.js";
@@ -4,6 +4,8 @@ import { EXIT, StromError } from "../core/errors.js";
4
4
  import { backupOnce, backupsDir, BackupFailed, mb } from "../core/backup.js";
5
5
  import { installation, replacedHolding } from "../core/self.js";
6
6
  import { ui } from "./ui.js";
7
+ import { isAgent } from "../core/which.js";
8
+ import { refusedCode, SANDBOX_HINT } from "../core/writable.js";
7
9
  /** A local date and time for a sentence. */
8
10
  function when(iso) {
9
11
  const d = new Date(iso);
@@ -50,17 +52,34 @@ export function backupBefore(ctx, t, opts = {}) {
50
52
  const where = backupsDir(home);
51
53
  const back = t.fromChannel ?? "stable";
52
54
  const params = { ...said, detail, where, back, channel: opts.update?.channel ?? "" };
55
+ // room on the disk only where the disk is full (found on Windows: an agent's sandbox refused the backup's lock, EPERM,
56
+ // and strom advised freeing room); a refused write says so — under an agent most likely its sandbox
57
+ const code = e instanceof BackupFailed ? e.code : e.code;
58
+ const why = (e instanceof BackupFailed && e.room) || code === "ENOSPC" ? "room" : refusedCode(code) ? "refused" : "other";
59
+ const agent = isAgent(ctx.env);
53
60
  if (opts.update)
54
61
  throw new StromError(`the backup before going from ${said.from} to ${said.to} could not be made: ${detail} — nothing was installed`, {
55
62
  exitCode: EXIT.noBackup,
56
- hint: `free room on the disk (${where}), then strom update --channel ${opts.update.channel} again`,
63
+ hint: why === "room"
64
+ ? `free room on the disk (${where}), then strom update --channel ${opts.update.channel} again`
65
+ : why === "refused"
66
+ ? `${agent ? `writing into ${where} was refused — ${SANDBOX_HINT}` : `writing into ${where} is refused: a folder with the right to write in it`}; then strom update --channel ${opts.update.channel} again`
67
+ : `strom doctor says more; then strom update --channel ${opts.update.channel} again`,
68
+ ...(why !== "room" ? { hintCode: `backup.failed.update.${why}` } : {}),
57
69
  code: "backup.failed.update",
58
70
  params,
59
71
  details: { from: t.from, to: t.to, where, ...(e instanceof BackupFailed && e.room ? { room: e.room } : {}) },
60
72
  });
61
73
  throw new StromError(`the backup before going from ${said.from} to ${said.to} could not be made: ${detail} — nothing is switched: this strom opens no family tree for writing until it is made (tried again at each run)`, {
62
74
  exitCode: EXIT.noBackup,
63
- hint: `tell the user: free room on the disk (${where}), then run strom again — or go back: strom update --channel ${back}`,
75
+ hint: why === "room"
76
+ ? `tell the user: free room on the disk (${where}), then run strom again — or go back: strom update --channel ${back}`
77
+ : why === "refused"
78
+ ? agent
79
+ ? `writing into ${where} was refused — ${SANDBOX_HINT}`
80
+ : `writing into ${where} is refused: a folder with the right to write in it, then run strom again — or go back: strom update --channel ${back}`
81
+ : `strom doctor says more; run strom again — or go back: strom update --channel ${back}`,
82
+ ...(why !== "room" ? { hintCode: `backup.failed.${why}` } : {}),
64
83
  code: "backup.failed",
65
84
  params,
66
85
  details: { from: t.from, to: t.to, where, ...(e instanceof BackupFailed && e.room ? { room: e.room } : {}) },
package/dist/cli/main.js CHANGED
@@ -2,14 +2,14 @@
2
2
  // and commit what a writing command changed. Returns the exit code.
3
3
  import fs from "node:fs";
4
4
  import { takeOverConnectors } from "../core/terms.js";
5
- import { notePersonFolder } from "../core/trust.js";
5
+ import { agentInAncestry, ancestryUnread, notePersonFolder } from "../core/trust.js";
6
6
  import { netTrust } from "../core/net.js";
7
7
  import { connectorsDir, listConnectors } from "../core/connector.js";
8
8
  import { Cancelled, EXIT, notFound, StromError, UsageError } from "../core/errors.js";
9
9
  import { ownCommand } from "../core/paths.js";
10
10
  import { Context } from "./context.js";
11
11
  import { commands, match } from "./registry.js";
12
- import { asCommand, asCommandJson } from "./format.js";
12
+ import { asCommand, asCommandJson, shellArg } from "./format.js";
13
13
  import { groupHelpAs, helpAs } from "./help.js";
14
14
  import { autoCommit } from "./commit.js";
15
15
  import { assertIntact } from "../core/integrity.js";
@@ -23,6 +23,7 @@ import { fireHooks, HOOK_INTERFACE } from "../core/hooks.js";
23
23
  import { prependPath } from "../runners/runner.js";
24
24
  import { shimDir } from "../commands/session.js";
25
25
  import { isAgent } from "../core/which.js";
26
+ import { noPermission } from "../core/writable.js";
26
27
  import { completePath, pathOf, pathToRemember } from "../core/userpath.js";
27
28
  import { appUrlShown, noticeStromApp } from "../core/stromapp.js";
28
29
  import { lastChannelOf, pendingTransition, updateChannel, whatsNewUrl } from "../core/update.js";
@@ -53,8 +54,11 @@ function print(io, ctx, result) {
53
54
  else if (result.text)
54
55
  io.stdout(result.text.endsWith("\n") ? result.text : result.text + "\n");
55
56
  }
56
- /** File-system errors in plain words (a missing @file, a folder without rights). */
57
- function systemError(e) {
57
+ /**
58
+ * File-system errors in plain words (a missing @file, a folder without rights — under an agent most likely its
59
+ * sandbox, core/writable.ts).
60
+ */
61
+ function systemError(e, agent) {
58
62
  const where = e.path ? `: ${e.path}` : "";
59
63
  // a file another program held past the waits (Windows: an antivirus, a sync tool, an editor) — no lack of rights:
60
64
  // running it again does it (what the command wrote was taken back)
@@ -65,9 +69,10 @@ function systemError(e) {
65
69
  return e.path ? notFound(e.path) : new UsageError("no such file or folder", { hint: "check the path (relative paths start in the current folder)" });
66
70
  case "EACCES":
67
71
  case "EPERM":
68
- return new UsageError(`no permission to use${where}`, { hint: "a folder with the right to write in it", code: "fs.no-permission", params: { where } });
72
+ case "EROFS":
73
+ return noPermission(e.path, agent);
69
74
  case "EISDIR":
70
- return new UsageError(`a folder was given where a file is expected${where}`);
75
+ return new UsageError(`a folder was given where a file is expected${where}`, { code: "fs.folder", ...(e.path ? { params: { path: e.path } } : {}) });
71
76
  case "ENOTDIR":
72
77
  return new UsageError(`a file was given where a folder is expected${where}`);
73
78
  case "ENOSPC":
@@ -97,8 +102,27 @@ function inLanguage(e, lang) {
97
102
  const say = (text) => placeholders(lang, text);
98
103
  return { message: say(localized(lang, key, message, e.params)), ...(e.hint ? { hint: say(hint ? localized(lang, hintKey, hint, e.params) : e.hint) } : {}), prefix: ui(lang, "ui.error.prefix") };
99
104
  }
100
- function printError(io, json, err, debug, lang) {
101
- const e = err instanceof StromError ? err : (systemError(err) ?? err);
105
+ const REFUSALS = new Set(["fs.no-permission", "git.unwritable", "init.unwritable"]);
106
+ /**
107
+ * A refusal to write under an agent: most likely its sandbox — said so, with what the agent does (core/writable.ts).
108
+ * Without an agent's marks, an agent among strom's parent processes (read here only, on a refusal: found on Windows,
109
+ * Codex's sandbox ran strom with none of its marks) is named to the person as the likely cause — not certain, so
110
+ * the other causes after it.
111
+ */
112
+ function underAgent(e, env) {
113
+ if (!REFUSALS.has(e.code ?? ""))
114
+ return e;
115
+ if (isAgent(env))
116
+ return noPermission(e.params?.where, env);
117
+ const above = agentInAncestry(env);
118
+ if (above)
119
+ return noPermission(e.params?.where, false, process.platform, above);
120
+ return ancestryUnread(env) ? noPermission(e.params?.where, false, process.platform, "?") : e;
121
+ }
122
+ function printError(io, json, err, debug, lang, env) {
123
+ const agent = isAgent(env) && env;
124
+ const sys = err instanceof StromError ? err : (systemError(err, agent) ?? err);
125
+ const e = sys instanceof StromError ? underAgent(sys, env) : sys;
102
126
  if (e instanceof StromError) {
103
127
  if (json)
104
128
  io.stdout(toJson(e.toJSON()));
@@ -221,6 +245,13 @@ export async function main(argv, io, env, cwd) {
221
245
  .map((x) => `${x[0].toUpperCase()}${x.slice(1).padStart(4, "0")}`);
222
246
  ran = ctx;
223
247
  command = def.path.join(" ");
248
+ // What the user does in their own terminal (a login, the seal, a plugin installed): refused to an agent here, after
249
+ // every option is read — the agents' rules match only what follows the command's name (strom --json seal adopt
250
+ // passed Codex's), so they are a second layer and strom the first
251
+ if (def.person && isAgent(env)) {
252
+ const said = (def.options ?? []).flatMap((o) => (v[o.name] === undefined || v[o.name] === false ? [] : o.type === "boolean" ? [`--${o.name}`] : [`--${o.name}`, String(v[o.name])]));
253
+ ctx.requireHuman(def.person, ["strom", ...def.path, ...[...parsed.positionals, ...said].map(shellArg)].join(" "), def.path[0], undefined, { window: false });
254
+ }
224
255
  // The PATH of the person's terminal remembered (never an agent's, a hook's, one strom started); a strom the system
225
256
  // started (a link from the Strom app, the bridge, the phone's server) gets what of it is missing — and so do the
226
257
  // agents and readers it starts (core/userpath.ts)
@@ -474,7 +505,7 @@ export async function main(argv, io, env, cwd) {
474
505
  // the usage is the agent's (or a program's): a person reads the error and its hint in their language, no English
475
506
  if (said instanceof StromError && said.usage && !json && !isAgent(env))
476
507
  said.usage = undefined;
477
- const code = printError(io, json, said, debug, lang);
508
+ const code = printError(io, json, said, debug, lang, env);
478
509
  remind(io, ran, command, Date.now() - startedAt);
479
510
  return code;
480
511
  }
@@ -118,6 +118,9 @@ export function describe(def, appUrl) {
118
118
  out.writes = true;
119
119
  if (def.tree)
120
120
  out.needsTree = true;
121
+ // the user's own, in their terminal: refused to an agent
122
+ if (def.person)
123
+ out.userOnly = true;
121
124
  if (def.aliases?.length)
122
125
  out.aliases = def.aliases.map((a) => a.join(" "));
123
126
  if (def.args?.length)