strom-research 1.7.0 → 1.8.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.
Files changed (42) hide show
  1. package/README.md +3 -0
  2. package/assets/lang/cs.json +117 -2
  3. package/assets/lang/de.json +117 -2
  4. package/assets/method/narrate.md +5 -1
  5. package/assets/plugins/gates/claude-usage/gate.ts +2 -1
  6. package/dist/agents/files.js +6 -4
  7. package/dist/cli/main.js +21 -3
  8. package/dist/cli/menu-links.js +35 -1
  9. package/dist/cli/menu-research.js +41 -2
  10. package/dist/cli/menu-settings.js +17 -1
  11. package/dist/cli/menu.js +19 -8
  12. package/dist/cli/ui.js +57 -1
  13. package/dist/commands/guide.js +6 -1
  14. package/dist/commands/links.js +39 -3
  15. package/dist/commands/pack.js +21 -18
  16. package/dist/commands/research.js +42 -1
  17. package/dist/commands/session.js +79 -9
  18. package/dist/commands/setup.js +3 -0
  19. package/dist/commands/start.js +6 -3
  20. package/dist/commands/story.js +47 -6
  21. package/dist/commands/tasks.js +15 -6
  22. package/dist/commands/trees.js +82 -2
  23. package/dist/core/changelog.js +223 -0
  24. package/dist/core/clock.js +20 -0
  25. package/dist/core/config.js +2 -0
  26. package/dist/core/connector.js +25 -1
  27. package/dist/core/directions.js +41 -0
  28. package/dist/core/links.js +17 -4
  29. package/dist/core/live.js +393 -50
  30. package/dist/core/pack.js +31 -0
  31. package/dist/core/phrases.js +61 -0
  32. package/dist/core/queue.js +7 -2
  33. package/dist/core/relocate.js +4 -4
  34. package/dist/core/session.js +8 -2
  35. package/dist/core/trash.js +72 -0
  36. package/dist/core/tree.js +1 -1
  37. package/dist/core/workers.js +28 -3
  38. package/dist/runners/claude.js +2 -1
  39. package/dist/runners/jsonl.js +2 -2
  40. package/dist/runners/runner.js +16 -3
  41. package/dist/runners/script.js +2 -1
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -105,6 +105,9 @@ npm: `npm install -g strom-research@latest`.
105
105
  double-click the launcher inside (Windows, macOS, Linux): it installs strom
106
106
  where it is missing, puts the research in place and takes it over
107
107
  (`strom unpack`)
108
+ - Take a family tree off the computer (`strom trees remove`): a backup offered
109
+ first, its name typed to confirm, then into the system's trash with the
110
+ images no other tree uses — never deleted for good
108
111
 
109
112
  **Safe by design**
110
113
  - What the agent may do alone is your choice: ask, auto (default) or full
@@ -179,6 +179,7 @@
179
179
  "ui.run.stop.resets": " (obnoví se {at})",
180
180
  "ui.run.stop.auth": "agent není přihlášený – spusťte jednou {command} a přihlaste se",
181
181
  "ui.run.stop.failed": "agent selhal: {detail}",
182
+ "ui.run.finishing": "■ Ctrl-C: sezení {session} dostalo žádost skončit – agent zapíše, co našel, a sezení uzavře, pak běh skončí (další sezení nezačne). Dalším Ctrl-C ho zastavíte hned.",
182
183
  "ui.run.stopping": "■ zastavuji – sezení se zavře a jeho úkol se vrátí do fronty; dalším Ctrl-C skončíte hned",
183
184
  "ui.run.gone": "· {session} zavřeno – jeho běh se zastavil{back}",
184
185
  "ui.run.back": "; {task} je zpět ve frontě",
@@ -374,6 +375,7 @@
374
375
  "ui.menu.app.live": "Agent pracuje – můžete ho živě sledovat v aplikaci Strom: volba {key}.",
375
376
  "ui.app.open.now": "Až bude nainstalovaná: otevřít v ní výzkum?",
376
377
  "ui.run.follow": "Sledovat mezitím práci živě v aplikaci Strom?",
378
+ "ui.run.follow.later": "Aplikace Strom se otevře, až agent začne první sezení.",
377
379
  "ui.consent.host.pace": "Vlastní tempo pro {host}: nejméně {seconds} s mezi dvěma požadavky, {cap}. Příliš rychlé stahování zpomalí archiv všem a může vést k zablokování vaší IP adresy – nastavit?",
378
380
  "ui.pace.cap": "nejvýš {n} za hodinu",
379
381
  "ui.pace.nocap": "bez hodinového limitu",
@@ -582,6 +584,14 @@
582
584
  "ui.consent.gate.set": "Nechat podmínku „{name}“ rozhodovat, kdy agent pracující sám pokračuje?",
583
585
  "ui.consent.gate.unset": "Odebrat podmínku „{name}“ — agent pracující sám se jí už nebude ptát?",
584
586
  "ui.consent.gate.skip": "Nechat agenta pracovat samotného bez podmínky „{name}“?",
587
+ "ui.run.browser.always": "· prohlížeč (Claude in Chrome) v každém sezení, jak jste nastavili (agent.browser always) – bez ptaní pro {hosts}; musí běžet Chrome s rozšířením Claude",
588
+ "ui.consent.browser": "Dát agentovi nástroje prohlížeče (Claude in Chrome) v každém sezení výzkumu? Pracuje ve vašem Chromu – tam, kde jste přihlášení – na webech archivů výzkumu bez ptaní; v rozhovoru se u každého jiného webu zeptá.",
589
+ "ui.archives.agentbrowser": "Prohlížeč pro agenta (Claude in Chrome): {state}",
590
+ "ui.archives.agentbrowser.archives": "jen pro archivy přes prohlížeč",
591
+ "ui.archives.agentbrowser.always": "v každém sezení",
592
+ "ui.archives.agentbrowser.about": "V každém sezení může agent pracovat ve vašem Chromu – tam, kde jste přihlášení – takže mu můžete zadat cokoli přes prohlížeč. Na webech archivů výzkumu bez ptaní; v rozhovoru se u každého jiného webu zeptá, při samostatné práci jiné weby nepoužije. Musí běžet Chrome s rozšířením Claude.",
593
+ "ui.archives.agentbrowser.on": "Dát agentovi prohlížeč v každém sezení?",
594
+ "ui.archives.agentbrowser.off": "Vrátit prohlížeč jen pro archivy přes prohlížeč?",
585
595
  "ui.consent.remote": "Spouštět sezení Claude Code, která strom otevírá, se vzdáleným ovládáním (Remote Control), abyste je mohli sledovat a řídit z claude.ai nebo z aplikace Claude v telefonu?",
586
596
  "ui.consent.hook.on": "Nechat hook „{name}“, aby se dozvídal všechno, co se uloží do vašich výzkumů? Je to program na tomto počítači a udělá s tím, k čemu byl napsán.",
587
597
  "ui.consent.hook.off": "Přestat hooku „{name}“ hlásit, co se ukládá?",
@@ -614,6 +624,16 @@
614
624
  "ui.link.what.chat.person": "rozhovor s agentem ({agent}) o osobě {person}.",
615
625
  "ui.link.chat.say": "Podívejme se na osobu {person}: řekni mi krátce, co o ní výzkum ví, a zeptej se mě, co chci zjistit.",
616
626
  "ui.link.what.review.person": "prověřit znovu osobu {person}.",
627
+ "ui.link.what.direction.pause": "pozastavit směr „{name}“: jeho úkoly ({n}) počkají mimo frontu a nic nového se pro něj nenavrhne. Nic se nemaže.",
628
+ "ui.link.what.direction.done": "ukončit směr „{name}“: jeho úkoly ({n}) odejdou z fronty. Nic se nemaže, jde ho znovu spustit.",
629
+ "ui.link.what.direction.resume": "znovu spustit směr „{name}“: jeho úkoly ({n}) se vrátí do fronty a výzkum navrhne, co chybí.",
630
+ "ui.link.direction.none": "Směr {id} v tomto výzkumu není. Nic se nestalo.",
631
+ "ui.link.direction.cannot": "Směr „{name}“ už je {state}. Nic se nestalo.",
632
+ "ui.link.direction.why": "Proč? (pár slov, zůstane u směru; Enter: bez důvodu)",
633
+ "ui.link.what.chat.research": "rozhovor s agentem ({agent}) o směru „{name}“, jen jeho úkoly.",
634
+ "ui.link.chat.research.say": "Pojďme pracovat na směru „{name}“ ({id}), na jeho úkolech.",
635
+ "ui.link.what.finish": "požádat agenta {agent}, aby dokončil sezení {session} ({task}): zapíše, co našel, a sezení zavře; při samostatné práci další nezačne.",
636
+ "ui.link.finish.none": "Sezení {session} už neběží (mezitím skončilo). Nic se nestalo.",
617
637
  "ui.link.what.review.family": "prověřit znovu osobu {person} i s partnery a dětmi.",
618
638
  "ui.link.what.review.line": "prověřit znovu osobu {person} i s předky.",
619
639
  "ui.link.what.research.ancestors": "hledat předky osoby {person} (nový směr výzkumu).",
@@ -703,7 +723,7 @@
703
723
  "ui.research.knows": "Co o tom víte? Data, místa a rodinné historky agentovi pomohou (Enter: nic)",
704
724
  "ui.research.done": "✓ Výzkum „{name}“ je založený.",
705
725
  "ui.research.chat": "Začít na něm s agentem hned?",
706
- "ui.research.say": "Založil(a) jsem nový výzkum „{name}“ – pojďme s ním začít.",
726
+ "ui.research.say": "Založil(a) jsem nový výzkum „{name}“ ({id}) – pojďme s ním začít, jeho úkoly.",
707
727
  "ui.settings.title": "Nastavení (na tomto počítači)",
708
728
  "ui.settings.wizard": "Jazyk, agent, model, co smí agent sám, příběhy… (průvodce nastavením)",
709
729
  "ui.settings.alone": "Samostatná práce agenta: {minutes} min na úkol · podmínka: {gate}",
@@ -750,6 +770,65 @@
750
770
  "ui.more.noagent": "Agent se do toho pustí, až bude na počítači (volba 1 v menu).",
751
771
  "ui.menu.noagent.chosen": "AI agent, kterého výzkum používá, na počítači není – volba 1 vybere jiného.",
752
772
  "task.answer": "Odpověď uživatele (na: {asked}): {answer}",
773
+ "who.run": "{agent} – samostatná práce",
774
+ "who.chat": "{agent} – rozhovor",
775
+ "log.person.add": "Nová osoba: {who}",
776
+ "log.person.name": "Zapsané jméno: {who}",
777
+ "log.person.edit": "Upraveno: {who}",
778
+ "log.person.cite": "Doklad k osobě {who}",
779
+ "log.person.retract": "Odvoláno: {who}",
780
+ "log.merge": "Sloučeno, byl to jeden: {who}",
781
+ "log.family.add": "Nová rodina: {who}",
782
+ "log.family.child": "Dítě rodiny {who}: {child}",
783
+ "log.family.edit": "Upravená rodina: {who}",
784
+ "log.family.cite": "Doklad k rodině {who}",
785
+ "log.event.add": "{who} – {fact}",
786
+ "log.event.edit": "Upřesněno: {who} – {fact}",
787
+ "log.event.cite": "Doklad k údaji: {who} – {fact}",
788
+ "log.event.retract": "Odvoláno: {who} – {fact}",
789
+ "log.source.add": "Záznam z pramene: {name}",
790
+ "log.source.edit": "Upravený záznam: {name}",
791
+ "log.search.found": "Hledáno, nalezeno: {name}",
792
+ "log.search.negative": "Hledáno, bez výsledku: {name}",
793
+ "log.search.partial": "Hledáno, nalezeno zčásti: {name}",
794
+ "log.search.inconclusive": "Hledáno, nejasné: {name}",
795
+ "log.recordset.add": "Nová kniha: {name}",
796
+ "log.recordset.edit": "Upravená kniha: {name}",
797
+ "log.recordset.calibrate": "V knize určeno, kde jsou které roky: {name}",
798
+ "log.media": "Nové snímky: {name} (obr. {images})",
799
+ "log.media.one": "Nový snímek: {name} (obr. {image})",
800
+ "log.media.loose": "Nové snímky: {n}",
801
+ "log.reading": "Pročteno: {name} (obr. {images})",
802
+ "log.task.add": "Nový úkol: {name}",
803
+ "log.task.done": "Hotovo: {name}",
804
+ "log.task.park": "Odloženo: {name}",
805
+ "log.task.drop": "Zahozeno: {name}",
806
+ "log.task.wait": "Čeká na vás: {name}",
807
+ "log.task.wake": "Zpět agentovi: {name}",
808
+ "log.task.edit": "Upravený úkol: {name}",
809
+ "log.session.start": "Agent začal: {name}",
810
+ "log.session.close": "Agent skončil: {name}",
811
+ "log.lesson.add": "Poznatek: {name}",
812
+ "log.hypothesis.add": "Hypotéza: {name}",
813
+ "log.hypothesis.argue": "Hypotéza zvážena: {name}",
814
+ "log.hypothesis.decide": "Hypotéza rozhodnuta: {name}",
815
+ "log.conflict.add": "Záznamy si odporují: {name}",
816
+ "log.conflict.resolve": "Rozpor vyřešen: {name}",
817
+ "log.place.add": "Nové místo: {name}",
818
+ "log.place.edit": "Upřesněné místo: {name}",
819
+ "log.repository.add": "Nový archiv: {name}",
820
+ "log.repository.edit": "Upravený archiv: {name}",
821
+ "log.story.set": "Vyprávění: {who}",
822
+ "log.story.approve": "Vyprávění schváleno: {who}",
823
+ "log.note.add": "Poznámka: {name}",
824
+ "log.input.add": "Materiál od rodiny: {name}",
825
+ "log.input.sync": "Převzatý rodokmen: {name}",
826
+ "log.input.done": "Materiál zpracován: {name}",
827
+ "log.research.add": "Nový směr výzkumu: {name}",
828
+ "log.research.edit": "Upravený směr výzkumu: {name}",
829
+ "log.research.pause": "Směr pozastaven: {name}",
830
+ "log.research.resume": "Směr znovu běží: {name}",
831
+ "log.research.done": "Směr ukončen: {name}",
753
832
  "ui.waiting.title": "Agent od vás potřebuje ({n}):",
754
833
  "ui.waiting.do": "Co udělat: {what}",
755
834
  "ui.waiting.ask": "agent vám to řekne v rozhovoru",
@@ -766,6 +845,20 @@
766
845
  "ui.intake.empty": "Není v tom nic, co by se dalo přidat.",
767
846
  "ui.intake.ours": "{path} je složka samotného stromu (výzkumu), ne rodinné podklady.",
768
847
  "ui.research.exists": "Takový výzkum už je: „{name}“ – agent v něm pokračuje.",
848
+ "ui.more.directions": "Směry výzkumu: pozastavit, ukončit nebo znovu spustit",
849
+ "ui.dirs.pick": "Který směr?",
850
+ "ui.dirs.item": "{name} – {state} · úkoly: {n}",
851
+ "ui.dirs.active": "běží",
852
+ "ui.dirs.paused": "pozastavený",
853
+ "ui.dirs.done": "ukončený",
854
+ "ui.dirs.what": "„{name}“:",
855
+ "ui.dirs.do.pause": "Pozastavit – jeho úkoly počkají, agent vezme jiné",
856
+ "ui.dirs.do.done": "Ukončit – jeho úkoly odejdou z fronty (nic se nemaže)",
857
+ "ui.dirs.do.resume": "Znovu spustit – jeho úkoly se vrátí do fronty",
858
+ "ui.dirs.paused.now": "⏸ „{name}“ je pozastavený: jeho úkoly ({n}) počkají, dokud ho tady znovu nespustíte.",
859
+ "ui.dirs.done.now": "■ „{name}“ je ukončený: jeho úkoly ({n}) jsou mimo frontu – nic se nesmazalo, tady ho jde znovu spustit.",
860
+ "ui.dirs.active.now": "▶ „{name}“ zase běží: jeho úkoly ({n}) jsou zpátky ve frontě.",
861
+ "ui.research.resume": "Takový výzkum už je, {state}: „{name}“. Spustit ho znovu?",
769
862
  "ui.alone.gate.bad": "Napište prosím počet bodů od 0 do 100 (třeba 10).",
770
863
  "ui.waiting.long": "Na odpověď sem je to dlouhé (nejvýš {n} znaků) – stačí odkaz nebo věta, zbytek řekněte agentovi v rozhovoru.",
771
864
  "ui.settings.remote.chats": "rozhovory ano, samostatná práce ne",
@@ -839,5 +932,27 @@
839
932
  "ui.unpack.same": "tento výzkum už tu je: {root}",
840
933
  "ui.unpack.person": "výzkum rozbaluje jen člověk u počítače (přidává konektory)",
841
934
  "ui.pack.launch.extract": "Nejdřív rozbalte celý soubor ZIP – ve Windows pravým tlačítkem: Extrahovat vše – a spusťte tohle znovu z rozbalené složky.",
842
- "ui.pack.launch.failed": "Strom Research se nepodařilo nainstalovat. Nainstalujte ho řádkem z JAK-NAVAZAT.txt a spusťte tohle znovu."
935
+ "ui.pack.launch.failed": "Strom Research se nepodařilo nainstalovat. Nainstalujte ho řádkem z JAK-NAVAZAT.txt a spusťte tohle znovu.",
936
+ "ui.trees.remove": "odebrat rodokmen z tohoto počítače",
937
+ "ui.session.finish.done": "Sezení {session} (úkol {task}) dostalo žádost skončit: agent ji uvidí u svého dalšího příkazu strom, zapíše, co našel, a sezení uzavře.",
938
+ "ui.session.finish.none": "v tomto výzkumu teď žádné sezení neběží",
939
+ "ui.session.finish.which": "běží víc sezení ({sessions}) – napište, které",
940
+ "ui.session.finish.closed": "sezení {session} už je uzavřené",
941
+ "ui.remove.which": "Který rodokmen odebrat z tohoto počítače?",
942
+ "ui.remove.person": "rodokmen z počítače odebírá jen člověk u počítače",
943
+ "ui.remove.busy": "na rodokmenu „{name}“ se právě pracuje – odebrat ho jde, až práce skončí",
944
+ "ui.remove.what": "„{name}“ ({root}) · osoby: {persons}",
945
+ "ui.remove.backup": "Nejdřív ho zabalit jako zálohu, se všemi snímky ({mb} MB)? Kdykoli se vrátí přes strom unpack.",
946
+ "ui.remove.backup.done": " záloha: {file}",
947
+ "ui.remove.images": "Poslat do koše i snímky, které žádný jiný rodokmen nepoužívá (snímky: {n}, {mb} MB)?",
948
+ "ui.remove.type": "Pro potvrzení napište název rodokmenu ({name}) – cokoli jiného nic neodebere:",
949
+ "ui.remove.cancelled": "Nic se neodebralo.",
950
+ "ui.remove.done": "„{name}“ je v koši.",
951
+ "ui.remove.live": " aplikace Strom ho už živě nesleduje",
952
+ "ui.remove.images.done": " jeho snímky v koši: {n} ({mb} MB)",
953
+ "ui.remove.images.folder": "{name} – snímky",
954
+ "ui.remove.backup.back": " vrátí se ze zálohy: strom unpack „{file}“",
955
+ "ui.remove.trash.alone": " vrátí se z koše systému, dokud ho nevysypete",
956
+ "ui.remove.next": "Teď pracujete s rodokmenem „{name}“ – jiný vyberete v nabídce pod Jiný rodokmen.",
957
+ "ui.remove.trash": " nebo z koše systému, dokud ho nevysypete"
843
958
  }
@@ -179,6 +179,7 @@
179
179
  "ui.run.stop.resets": " (erneuert sich {at})",
180
180
  "ui.run.stop.auth": "der Agent ist nicht angemeldet – starten Sie einmal {command} und melden Sie sich an",
181
181
  "ui.run.stop.failed": "der Agent ist gescheitert: {detail}",
182
+ "ui.run.finishing": "■ Strg-C: Sitzung {session} ist gebeten aufzuhören – der Agent schreibt auf, was er gefunden hat, und schließt sie, dann endet der Lauf (keine nächste Sitzung). Noch einmal Strg-C stoppt ihn sofort.",
182
183
  "ui.run.stopping": "■ wird angehalten – die Sitzung wird geschlossen und ihre Aufgabe kehrt in die Warteschlange zurück; nochmals Strg-C beendet sofort",
183
184
  "ui.run.gone": "· {session} geschlossen – ihr Lauf war abgebrochen{back}",
184
185
  "ui.run.back": "; {task} ist zurück in der Warteschlange",
@@ -374,6 +375,7 @@
374
375
  "ui.menu.app.live": "Der Agent arbeitet – Sie können ihm in der Strom-App live zusehen: Auswahl {key}.",
375
376
  "ui.app.open.now": "Sobald sie installiert ist: die Forschung darin öffnen?",
376
377
  "ui.run.follow": "Die Arbeit währenddessen live in der Strom-App verfolgen?",
378
+ "ui.run.follow.later": "Die Strom-App öffnet sich, wenn der Agent seine erste Sitzung beginnt.",
377
379
  "ui.consent.host.pace": "Ihr eigenes Tempo für {host}: mindestens {seconds} s zwischen zwei Anfragen, {cap}. Zu schnelles Laden bremst das Archiv für alle und kann Ihre IP-Adresse sperren lassen – so einstellen?",
378
380
  "ui.pace.cap": "höchstens {n} pro Stunde",
379
381
  "ui.pace.nocap": "ohne Stundenlimit",
@@ -582,6 +584,14 @@
582
584
  "ui.consent.gate.set": "Die Bedingung „{name}“ entscheiden lassen, wann der allein arbeitende Agent weitermacht?",
583
585
  "ui.consent.gate.unset": "Die Bedingung „{name}“ entfernen — der allein arbeitende Agent fragt sie nicht mehr?",
584
586
  "ui.consent.gate.skip": "Den Agenten ohne die Bedingung „{name}“ allein arbeiten lassen?",
587
+ "ui.run.browser.always": "· der Browser (Claude in Chrome) in jeder Sitzung, wie Sie es eingestellt haben (agent.browser always) – ohne Nachfrage für {hosts}; Chrome mit der Claude-Erweiterung muss laufen",
588
+ "ui.consent.browser": "Dem Agenten in jeder Sitzung der Forschung Browser-Werkzeuge (Claude in Chrome) geben? Er arbeitet in Ihrem eigenen Chrome – wo Sie angemeldet sind – auf den Seiten der Archive der Forschung ohne Nachfrage; im Gespräch fragt er bei jeder anderen Seite.",
589
+ "ui.archives.agentbrowser": "Der Browser für den Agenten (Claude in Chrome): {state}",
590
+ "ui.archives.agentbrowser.archives": "nur für Archive über den Browser",
591
+ "ui.archives.agentbrowser.always": "in jeder Sitzung",
592
+ "ui.archives.agentbrowser.about": "In jeder Sitzung kann der Agent in Ihrem eigenen Chrome arbeiten – wo Sie angemeldet sind –, Sie können ihm also alles über den Browser auftragen. Die Seiten der Archive der Forschung ohne Nachfrage; im Gespräch fragt er bei jeder anderen Seite, allein arbeitend nutzt er keine andere. Chrome mit der Claude-Erweiterung muss laufen.",
593
+ "ui.archives.agentbrowser.on": "Dem Agenten den Browser in jeder Sitzung geben?",
594
+ "ui.archives.agentbrowser.off": "Zurück zum Browser nur für Archive über den Browser?",
585
595
  "ui.consent.remote": "Die Claude-Code-Sitzungen, die strom öffnet, mit Remote Control starten, damit Sie sie auf claude.ai oder in der Claude-App auf dem Telefon verfolgen und steuern können?",
586
596
  "ui.consent.hook.on": "Den Hook „{name}“ über alles informieren, was in Ihre Forschungen gespeichert wird? Er ist ein Programm auf diesem Computer und tut damit, wofür er geschrieben wurde.",
587
597
  "ui.consent.hook.off": "Dem Hook „{name}“ nicht mehr melden, was gespeichert wird?",
@@ -614,6 +624,16 @@
614
624
  "ui.link.what.chat.person": "ein Gespräch mit dem Agenten ({agent}) über {person}.",
615
625
  "ui.link.chat.say": "Schauen wir uns {person} an: Sag mir kurz, was die Forschung über diese Person weiß, und frag mich, was ich herausfinden möchte.",
616
626
  "ui.link.what.review.person": "{person} erneut prüfen.",
627
+ "ui.link.what.direction.pause": "die Richtung „{name}“ pausieren: ihre Aufgaben ({n}) warten außerhalb der Warteschlange, und es wird nichts Neues für sie vorgeschlagen. Nichts wird gelöscht.",
628
+ "ui.link.what.direction.done": "die Richtung „{name}“ beenden: ihre Aufgaben ({n}) verlassen die Warteschlange. Nichts wird gelöscht, sie kann wieder aufgenommen werden.",
629
+ "ui.link.what.direction.resume": "die Richtung „{name}“ wieder aufnehmen: ihre Aufgaben ({n}) kommen zurück in die Warteschlange, und die Forschung schlägt vor, was fehlt.",
630
+ "ui.link.direction.none": "Richtung {id} ist nicht in dieser Forschung. Es ist nichts geschehen.",
631
+ "ui.link.direction.cannot": "Die Richtung „{name}“ ist schon {state}. Es ist nichts geschehen.",
632
+ "ui.link.direction.why": "Warum? (ein paar Worte, bleiben bei der Richtung; Enter: ohne Grund)",
633
+ "ui.link.what.chat.research": "ein Gespräch mit dem Agenten ({agent}) über die Richtung „{name}“, nur ihre Aufgaben.",
634
+ "ui.link.chat.research.say": "Arbeiten wir an der Richtung „{name}“ ({id}), an ihren Aufgaben.",
635
+ "ui.link.what.finish": "{agent} bitten, die Sitzung {session} ({task}) abzuschließen: er schreibt auf, was er gefunden hat, und schließt die Sitzung; arbeitet er selbständig, beginnt er keine weitere.",
636
+ "ui.link.finish.none": "Die Sitzung {session} läuft nicht mehr (sie ist inzwischen beendet). Es ist nichts geschehen.",
617
637
  "ui.link.what.review.family": "{person} erneut prüfen, mit Partnern und Kindern.",
618
638
  "ui.link.what.review.line": "{person} erneut prüfen, mit den Vorfahren.",
619
639
  "ui.link.what.research.ancestors": "die Vorfahren von {person} suchen (eine neue Richtung der Forschung).",
@@ -703,7 +723,7 @@
703
723
  "ui.research.knows": "Was wissen Sie darüber? Daten, Orte und Familiengeschichten helfen dem Agenten (Enter: nichts)",
704
724
  "ui.research.done": "✓ Die Forschung „{name}“ ist angelegt.",
705
725
  "ui.research.chat": "Jetzt mit dem Agenten beginnen?",
706
- "ui.research.say": "Ich habe eine neue Forschung angelegt, „{name}“ – fangen wir damit an.",
726
+ "ui.research.say": "Ich habe eine neue Forschung angelegt, „{name}“ ({id}) – fangen wir damit an, mit ihren Aufgaben.",
707
727
  "ui.settings.title": "Einstellungen (auf diesem Computer)",
708
728
  "ui.settings.wizard": "Sprache, Agent, Modell, was der Agent allein darf, Geschichten… (die Einrichtung)",
709
729
  "ui.settings.alone": "Der Agent allein: {minutes} Min. je Aufgabe · Bedingung: {gate}",
@@ -750,6 +770,65 @@
750
770
  "ui.more.noagent": "Der Agent nimmt es auf, sobald er auf dem Computer ist (Punkt 1 im Menü).",
751
771
  "ui.menu.noagent.chosen": "Der KI-Agent dieser Forschung ist nicht auf dem Computer – Punkt 1 wählt einen anderen.",
752
772
  "task.answer": "Antwort des Nutzers (auf: {asked}): {answer}",
773
+ "who.run": "{agent} – arbeitet selbständig",
774
+ "who.chat": "{agent} – Gespräch",
775
+ "log.person.add": "Neue Person: {who}",
776
+ "log.person.name": "Name eingetragen: {who}",
777
+ "log.person.edit": "Geändert: {who}",
778
+ "log.person.cite": "Ein Beleg für {who}",
779
+ "log.person.retract": "Zurückgezogen: {who}",
780
+ "log.merge": "Zusammengeführt, es war derselbe: {who}",
781
+ "log.family.add": "Neue Familie: {who}",
782
+ "log.family.child": "Ein Kind von {who}: {child}",
783
+ "log.family.edit": "Familie geändert: {who}",
784
+ "log.family.cite": "Ein Beleg für die Familie {who}",
785
+ "log.event.add": "{who} – {fact}",
786
+ "log.event.edit": "Präzisiert: {who} – {fact}",
787
+ "log.event.cite": "Ein Beleg für: {who} – {fact}",
788
+ "log.event.retract": "Zurückgezogen: {who} – {fact}",
789
+ "log.source.add": "Eintrag aus einer Quelle: {name}",
790
+ "log.source.edit": "Eintrag geändert: {name}",
791
+ "log.search.found": "Gesucht, gefunden: {name}",
792
+ "log.search.negative": "Gesucht, nichts gefunden: {name}",
793
+ "log.search.partial": "Gesucht, teilweise gefunden: {name}",
794
+ "log.search.inconclusive": "Gesucht, unklar: {name}",
795
+ "log.recordset.add": "Neues Buch: {name}",
796
+ "log.recordset.edit": "Buch geändert: {name}",
797
+ "log.recordset.calibrate": "Im Buch bestimmt, wo welche Jahre stehen: {name}",
798
+ "log.media": "Neue Bilder: {name} (Bilder {images})",
799
+ "log.media.one": "Neues Bild: {name} (Bild {image})",
800
+ "log.media.loose": "Neue Bilder: {n}",
801
+ "log.reading": "Durchgelesen: {name} (Bilder {images})",
802
+ "log.task.add": "Neue Aufgabe: {name}",
803
+ "log.task.done": "Erledigt: {name}",
804
+ "log.task.park": "Zurückgestellt: {name}",
805
+ "log.task.drop": "Verworfen: {name}",
806
+ "log.task.wait": "Wartet auf Sie: {name}",
807
+ "log.task.wake": "Zurück an den Agenten: {name}",
808
+ "log.task.edit": "Aufgabe geändert: {name}",
809
+ "log.session.start": "Der Agent beginnt: {name}",
810
+ "log.session.close": "Der Agent ist fertig: {name}",
811
+ "log.lesson.add": "Gelernt: {name}",
812
+ "log.hypothesis.add": "Eine Hypothese: {name}",
813
+ "log.hypothesis.argue": "Hypothese abgewogen: {name}",
814
+ "log.hypothesis.decide": "Hypothese entschieden: {name}",
815
+ "log.conflict.add": "Die Einträge widersprechen sich: {name}",
816
+ "log.conflict.resolve": "Widerspruch geklärt: {name}",
817
+ "log.place.add": "Neuer Ort: {name}",
818
+ "log.place.edit": "Ort präzisiert: {name}",
819
+ "log.repository.add": "Neues Archiv: {name}",
820
+ "log.repository.edit": "Archiv geändert: {name}",
821
+ "log.story.set": "Erzählung: {who}",
822
+ "log.story.approve": "Erzählung freigegeben: {who}",
823
+ "log.note.add": "Notiz: {name}",
824
+ "log.input.add": "Material der Familie: {name}",
825
+ "log.input.sync": "Stammbaum übernommen: {name}",
826
+ "log.input.done": "Material durchgearbeitet: {name}",
827
+ "log.research.add": "Neue Richtung der Forschung: {name}",
828
+ "log.research.edit": "Richtung geändert: {name}",
829
+ "log.research.pause": "Richtung pausiert: {name}",
830
+ "log.research.resume": "Richtung wieder aufgenommen: {name}",
831
+ "log.research.done": "Richtung beendet: {name}",
753
832
  "ui.waiting.title": "Der Agent braucht Sie ({n}):",
754
833
  "ui.waiting.do": "Was zu tun ist: {what}",
755
834
  "ui.waiting.ask": "der Agent sagt es Ihnen im Gespräch",
@@ -766,6 +845,20 @@
766
845
  "ui.intake.empty": "Darin ist nichts, was sich hinzufügen ließe.",
767
846
  "ui.intake.ours": "{path} ist ein Ordner von strom selbst (der Forschung), keine Unterlagen der Familie.",
768
847
  "ui.research.exists": "Diese Forschung gibt es schon: „{name}“ – der Agent arbeitet daran weiter.",
848
+ "ui.more.directions": "Die Richtungen der Forschung: pausieren, beenden oder wieder aufnehmen",
849
+ "ui.dirs.pick": "Welche Richtung?",
850
+ "ui.dirs.item": "{name} – {state} · Aufgaben: {n}",
851
+ "ui.dirs.active": "läuft",
852
+ "ui.dirs.paused": "pausiert",
853
+ "ui.dirs.done": "beendet",
854
+ "ui.dirs.what": "„{name}“:",
855
+ "ui.dirs.do.pause": "Pausieren – ihre Aufgaben warten, der Agent nimmt andere",
856
+ "ui.dirs.do.done": "Beenden – ihre Aufgaben verlassen die Warteschlange (nichts wird gelöscht)",
857
+ "ui.dirs.do.resume": "Wieder aufnehmen – ihre Aufgaben zurück in die Warteschlange",
858
+ "ui.dirs.paused.now": "⏸ „{name}“ ist pausiert: ihre Aufgaben ({n}) warten, bis Sie sie hier wieder aufnehmen.",
859
+ "ui.dirs.done.now": "■ „{name}“ ist beendet: ihre Aufgaben ({n}) sind aus der Warteschlange – nichts gelöscht, hier lässt sie sich wieder aufnehmen.",
860
+ "ui.dirs.active.now": "▶ „{name}“ läuft wieder: ihre Aufgaben ({n}) sind zurück in der Warteschlange.",
861
+ "ui.research.resume": "Diese Forschung gibt es schon, {state}: „{name}“. Wieder aufnehmen?",
769
862
  "ui.alone.gate.bad": "Bitte eine Zahl von Punkten zwischen 0 und 100 (z. B. 10).",
770
863
  "ui.waiting.long": "Das ist für eine Antwort hier zu lang (höchstens {n} Zeichen) – ein Link oder ein Satz; das Übrige sagen Sie dem Agenten im Gespräch.",
771
864
  "ui.settings.remote.chats": "Gespräche ja, allein arbeiten nein",
@@ -839,5 +932,27 @@
839
932
  "ui.unpack.same": "diese Forschung ist schon da: {root}",
840
933
  "ui.unpack.person": "eine Forschung packt nur die Person am Computer aus (sie fügt Konnektoren hinzu)",
841
934
  "ui.pack.launch.extract": "Packen Sie zuerst die ganze ZIP-Datei aus – unter Windows mit Rechtsklick: Alle extrahieren – und starten Sie dies dann im ausgepackten Ordner neu.",
842
- "ui.pack.launch.failed": "Strom Research ließ sich nicht installieren. Installieren Sie es mit der Zeile aus SO-GEHT-ES-WEITER.txt und starten Sie dies neu."
935
+ "ui.pack.launch.failed": "Strom Research ließ sich nicht installieren. Installieren Sie es mit der Zeile aus SO-GEHT-ES-WEITER.txt und starten Sie dies neu.",
936
+ "ui.trees.remove": "einen Stammbaum von diesem Computer nehmen",
937
+ "ui.session.finish.done": "Sitzung {session} (Aufgabe {task}) ist gebeten, aufzuhören: ihr Agent sieht es bei seinem nächsten strom-Befehl, schreibt auf, was er gefunden hat, und schließt die Sitzung.",
938
+ "ui.session.finish.none": "in dieser Forschung läuft gerade keine Sitzung",
939
+ "ui.session.finish.which": "mehrere Sitzungen laufen ({sessions}) – sagen Sie, welche",
940
+ "ui.session.finish.closed": "Sitzung {session} ist schon geschlossen",
941
+ "ui.remove.which": "Welchen Stammbaum von diesem Computer nehmen?",
942
+ "ui.remove.person": "einen Stammbaum nimmt nur die Person am Computer herunter",
943
+ "ui.remove.busy": "an „{name}“ wird gerade gearbeitet – er kann weg, wenn die Arbeit vorbei ist",
944
+ "ui.remove.what": "„{name}“ ({root}) · Personen: {persons}",
945
+ "ui.remove.backup": "Zuerst als Sicherung einpacken, mit allen Bildern ({mb} MB)? Er kommt jederzeit mit strom unpack zurück.",
946
+ "ui.remove.backup.done": " Sicherung: {file}",
947
+ "ui.remove.images": "Auch die Bilder, die kein anderer Stammbaum nutzt, in den Papierkorb ({n}, {mb} MB)?",
948
+ "ui.remove.type": "Zur Bestätigung tippen Sie den Namen des Stammbaums ({name}) – alles andere nimmt nichts weg:",
949
+ "ui.remove.cancelled": "Nichts wurde weggenommen.",
950
+ "ui.remove.done": "„{name}“ ist im Papierkorb.",
951
+ "ui.remove.live": " die Strom-App verfolgt ihn nicht mehr live",
952
+ "ui.remove.images.done": " seine Bilder im Papierkorb: {n} ({mb} MB)",
953
+ "ui.remove.images.folder": "{name} – Bilder",
954
+ "ui.remove.backup.back": " er kommt aus der Sicherung zurück: strom unpack „{file}“",
955
+ "ui.remove.trash.alone": " er kommt aus dem Papierkorb des Systems zurück, bis er geleert wird",
956
+ "ui.remove.next": "Jetzt arbeiten Sie an „{name}“ – einen anderen wählen Sie im Menü unter Anderer Stammbaum.",
957
+ "ui.remove.trash": " oder aus dem Papierkorb des Systems, bis er geleert wird"
843
958
  }
@@ -7,8 +7,12 @@ A story is written from the facts, for the family.
7
7
  unknown. Never fill gaps with plausible fiction.
8
8
  - Use the research language and plain words; explain old occupations and
9
9
  terms the reader will not know.
10
+ - Format: paragraphs separated by a blank line; within them only "## "
11
+ subheadings, "- " bullet lines, **bold** and *italic* — the Strom app shows
12
+ anything else (links, tables, quotes, numbered lists) as plain text. The
13
+ title goes in --title, not as a "# " line in the text.
10
14
  - Write it to a file in notes/, then `strom story set P… --text @notes/story-P….md
11
- --fact E… --fact E… --note "what is inferred"`: every fact it leans on goes
15
+ --title "…" --fact E… --fact E… --note "what is inferred"`: every fact it leans on goes
12
16
  in --fact. A couple's story goes on the family (F…).
13
17
  - It stays a draft; when the user says it is right: `strom story approve P…`
14
18
  (the Strom app asks them too).
@@ -87,7 +87,8 @@ const now = Date.now();
87
87
  const start = week.resets.getTime() - WEEK;
88
88
  const gone = Math.max(0, Math.min(100, ((now - start) / WEEK) * 100));
89
89
  const behind = gone - week.used;
90
- const v = { used: week.used, gone: Math.round(gone), behind: Math.round(behind), min };
90
+ // shown rounded down: never "15 in hand (at least 15 needed)" for 14.6 — and gone − used stays what it says
91
+ const v = { used: week.used, gone: Math.floor(gone), behind: Math.floor(behind), min };
91
92
  if (session && session.used >= 100) answer(1, say("session"), session.resets ? new Date(session.resets.getTime() + 60_000) : undefined);
92
93
  if (behind < min) {
93
94
  // when the week gone reaches usage + the number; past the reset, the new week's number
@@ -12,7 +12,7 @@ import { writeFileAtomic } from "../core/json.js";
12
12
  import { PROFILES, SELF_READING } from "./profiles.js";
13
13
  import { Settings } from "../core/config.js";
14
14
  import { configDir } from "../core/paths.js";
15
- import { treeBrowserConnectors } from "../core/connector.js";
15
+ import { agentBrowser } from "../core/connector.js";
16
16
  import { CHROME_ALLOW, CHROME_DENY, chromeDomain } from "../core/browser.js";
17
17
  export const MARKER = "<!-- strom: generated above this line (strom agents sync); your own notes below are kept -->";
18
18
  function agentsMd(tree) {
@@ -152,8 +152,10 @@ function treeRules(tree, agent) {
152
152
  const claude = agent === "claude";
153
153
  const at = (abs) => (claude ? permissionPath(abs) : abs.replace(/\\/g, "/"));
154
154
  const keys = at(configDir(tree.env));
155
- // Connectors of this tree whose images come through the user's browser: browser tools (Claude in Chrome), for their sites only.
156
- const sites = claude ? [...new Set(treeBrowserConnectors(tree, shared).flatMap((c) => c.manifest.hosts.map(chromeDomain)))] : [];
155
+ // Browser tools (Claude in Chrome): for the sites of the connectors fetching through the browser — or, agent.browser
156
+ // always, of every archive of the research (anything else is asked in a conversation, not used in a run).
157
+ const web = claude ? agentBrowser(tree, shared) : { on: false, hosts: [] };
158
+ const sites = [...new Set(web.hosts.map(chromeDomain))];
157
159
  const downloads = at(settings.downloads());
158
160
  // A rule for each of Claude Code's shells: Bash, and PowerShell (on Windows) — a Bash rule does not cover it. Grok has one.
159
161
  const shell = (cmd) => (claude ? [`Bash(${cmd})`, `PowerShell(${cmd})`] : [`Bash(${cmd})`]);
@@ -171,7 +173,7 @@ function treeRules(tree, agent) {
171
173
  ...(shared ? ["Read", "Edit"].map((t) => `${t}(${at(path.join(shared, "plugins", "connectors"))}/**)`) : []),
172
174
  "WebSearch",
173
175
  "WebFetch",
174
- ...(sites.length ? [...CHROME_ALLOW, ...sites] : []),
176
+ ...(web.on ? [...CHROME_ALLOW, ...sites] : []),
175
177
  ],
176
178
  deny: [
177
179
  "Read(data/**)",
package/dist/cli/main.js CHANGED
@@ -15,7 +15,7 @@ import { noticeStromApp } from "../core/stromapp.js";
15
15
  import { isNewer } from "../core/update.js";
16
16
  import { refreshGlobal } from "../agents/global.js";
17
17
  import { linkFiles, linkHandlerState, registerLinks } from "../core/links.js";
18
- import { clockLine } from "../core/clock.js";
18
+ import { clockLine, FINISH_LINE, finishAsked } from "../core/clock.js";
19
19
  import { currentSession } from "../core/session.js";
20
20
  import { checkArgs, GroupOnly, parseOptions, resolveCommand, splitPassthrough } from "./execute.js";
21
21
  import "../commands/index.js";
@@ -194,9 +194,27 @@ export async function main(argv, io, env, cwd) {
194
194
  return code;
195
195
  }
196
196
  }
197
- /** Near the end of a session with a time limit (strom run), every command reminds the agent how long it has left. */
197
+ /**
198
+ * Near the end of a session with a time limit (strom run), every command reminds the agent how long it has left;
199
+ * a session the user asked to finish (strom session finish) is told to write down and close, from its next command.
200
+ */
198
201
  function remind(io, ctx, command) {
199
- const line = ctx && command !== "session close" ? clockLine(ctx.env) : undefined;
202
+ if (!ctx || command === "session close" || command === "session finish")
203
+ return;
204
+ const session = ctx.env.STROM_SESSION;
205
+ if (session) {
206
+ try {
207
+ const root = ctx.locateTree();
208
+ if (root && finishAsked(root, session)) {
209
+ io.stderr(FINISH_LINE + "\n");
210
+ return;
211
+ }
212
+ }
213
+ catch {
214
+ // no tree here: only the clock
215
+ }
216
+ }
217
+ const line = clockLine(ctx.env);
200
218
  if (!line)
201
219
  return;
202
220
  try {
@@ -6,7 +6,7 @@
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import { outOfAnswers, translator } from "./menu-parts.js";
9
- import { offerChat } from "./menu-research.js";
9
+ import { directionTasks, offerChat, setDirection } from "./menu-research.js";
10
10
  import { humanCost, humanDay, humanTask } from "./human.js";
11
11
  import { lines, truncate } from "./format.js";
12
12
  import { Tree } from "../core/tree.js";
@@ -55,6 +55,40 @@ export async function taskDo(ctx, run, lang, root, id, what) {
55
55
  out(ctx, t(`ui.link.task.${what}.done`));
56
56
  return true;
57
57
  }
58
+ /** A direction of the research paused, ended (each with the person's reason, if they give one) or taken up again. */
59
+ export async function directionDo(ctx, run, lang, root, id, what) {
60
+ const t = translator(lang);
61
+ const tree = Tree.open(root, ctx.env);
62
+ const r = tree.get(id);
63
+ if (!r || r.type !== "research")
64
+ return stop(ctx, t("ui.link.direction.none", { id }));
65
+ const state = what === "pause" ? "paused" : what === "resume" ? "active" : "done";
66
+ if (r.state === state)
67
+ return stop(ctx, state === "active" ? t("ui.research.exists", { name: r.name }) : t("ui.link.direction.cannot", { name: r.name, state: t(`ui.dirs.${state}`) }));
68
+ if (!(await asks(ctx, lang, root, t(`ui.link.what.direction.${what}`, { name: r.name, n: directionTasks(tree, r.id) }))))
69
+ return false;
70
+ let reason;
71
+ if (what !== "resume") {
72
+ reason = (await ctx.ask(t("ui.link.direction.why"))).trim();
73
+ if (reason === "0")
74
+ return false;
75
+ }
76
+ await setDirection(ctx, run, lang, root, r, what, reason || undefined);
77
+ return true;
78
+ }
79
+ /** A session at work asked to finish: its agent writes down what it found and closes it; a run starts no next one. */
80
+ export async function finishSession(ctx, run, lang, root, id) {
81
+ const t = translator(lang);
82
+ const tree = Tree.open(root, ctx.env);
83
+ const s = tree.get(id);
84
+ if (!s || s.type !== "session" || s.state !== "open")
85
+ return stop(ctx, t("ui.link.finish.none", { session: id }));
86
+ const task = s.task ? tree.get(s.task) : undefined;
87
+ const agent = PROFILES[s.agent ?? ""]?.name ?? s.agent ?? "";
88
+ if (!(await asks(ctx, lang, root, t("ui.link.what.finish", { agent, session: s.id, task: task ? truncate(humanTask(tree, task.what, lang), 80) : "–" }))))
89
+ return false;
90
+ return (await run(["session", "finish", s.id])) === 0;
91
+ }
58
92
  /** The agent's sessions, the latest first, and what this month's cost. */
59
93
  export function sessionsView(ctx, lang, root) {
60
94
  const t = translator(lang);
@@ -6,6 +6,7 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { agentReady, droppedPaths, outOfAnswers, pause, pickPerson, subMenu, translator } from "./menu-parts.js";
8
8
  import { Tree } from "../core/tree.js";
9
+ import { directionOf, scopes } from "../core/directions.js";
9
10
  import { displayName, lifespan } from "../core/people.js";
10
11
  import { DEATH_AFTER_YEARS, unprovenPeople } from "../core/review.js";
11
12
  import { planSync, readTreeFile, receivedSince } from "../core/sync.js";
@@ -26,10 +27,12 @@ export async function addToResearch(ctx, run, lang, root) {
26
27
  items.push({ key: "3", label: t("ui.menu.review"), act: async () => void (await reviewPerson(ctx, run, lang, root)) });
27
28
  items.push({ key: "4", label: t(noApp(ctx) ? "ui.more.sync.noapp" : "ui.more.sync"), act: async () => void (await syncTree(ctx, run, lang, root)) });
28
29
  }
30
+ if (tree.count("research") > 0)
31
+ items.push({ key: "5", label: t("ui.more.directions"), act: async () => void (await directions(ctx, run, lang, root)) });
29
32
  // only when there are some: the people no record of their own proves — last, it shows only sometimes
30
33
  const unproven = unprovenPeople(tree).filter((u) => !u.living).length;
31
34
  if (unproven)
32
- items.push({ key: "5", label: t("ui.menu.unproven", { n: unproven }), act: async () => void (await reviewUnproven(ctx, run, lang, root)) });
35
+ items.push({ key: String(items.length + 1), label: t("ui.menu.unproven", { n: unproven }), act: async () => void (await reviewUnproven(ctx, run, lang, root)) });
33
36
  return { title: t("ui.more.title"), items };
34
37
  });
35
38
  }
@@ -175,6 +178,13 @@ export async function startDirection(ctx, run, lang, root, direction, who, name,
175
178
  : undefined;
176
179
  if (same)
177
180
  return void ctx.io.stdout(t("ui.research.exists", { name: same.name }) + "\n");
181
+ // …paused or ended: taken up again rather than made twice
182
+ const stopped = focus && direction !== "question" ? Tree.open(root, ctx.env).list("research").find((r) => r.focus === focus && r.direction === direction) : undefined;
183
+ if (stopped) {
184
+ if (await ctx.confirm(t("ui.research.resume", { name: stopped.name, state: t(`ui.dirs.${stopped.state}`) })))
185
+ await setDirection(ctx, run, lang, root, stopped, "resume");
186
+ return;
187
+ }
178
188
  const suggested = question ?? t(direction === "ancestors" ? "ui.research.title.ancestors" : "ui.research.title.descendants", { name });
179
189
  const title = (await ctx.ask(t("ui.research.title"), suggested)).trim();
180
190
  if (title === "0")
@@ -193,7 +203,36 @@ export async function startDirection(ctx, run, lang, root, direction, who, name,
193
203
  if (knows && knows !== "0")
194
204
  await run(["intake", `--text=${knows}`, "--research", made.id], true);
195
205
  ctx.io.stdout(t("ui.research.done", { name: made.name }) + "\n");
196
- await offerChat(ctx, run, lang, root, t("ui.research.chat"), t("ui.research.say", { name: made.name }));
206
+ await offerChat(ctx, run, lang, root, t("ui.research.chat"), t("ui.research.say", { name: made.name, id: made.id }));
207
+ }
208
+ /** A direction's tasks not finished: open, at work, put aside or waiting for the user. */
209
+ export function directionTasks(tree, research) {
210
+ const all = scopes(tree);
211
+ return tree.list("task").filter((x) => ["open", "doing", "parked", "waiting"].includes(x.state) && directionOf(tree, x, all) === research).length;
212
+ }
213
+ /** The directions of the research, each with how it goes and its tasks: one paused, ended or taken up again. */
214
+ async function directions(ctx, run, lang, root) {
215
+ const t = translator(lang);
216
+ const tree = Tree.open(root, ctx.env);
217
+ const all = tree.list("research");
218
+ const label = (r) => t("ui.dirs.item", { name: r.name, state: t(`ui.dirs.${r.state}`), n: directionTasks(tree, r.id) });
219
+ const i = await ctx.choose(t("ui.dirs.pick"), all.map((r) => ({ label: label(r) })), all.length, { back: t("ui.browse.back") });
220
+ if (i === undefined)
221
+ return;
222
+ const r = all[i];
223
+ const acts = r.state === "active" ? ["pause", "done"] : r.state === "paused" ? ["resume", "done"] : ["resume"];
224
+ const a = await ctx.choose(t("ui.dirs.what", { name: r.name }), acts.map((x) => ({ label: t(`ui.dirs.do.${x}`) })), acts.length, { back: t("ui.browse.back") });
225
+ if (a === undefined)
226
+ return;
227
+ await setDirection(ctx, run, lang, root, r, acts[a]);
228
+ }
229
+ /** A direction paused, ended or taken up again (with the person's reason), and what that means for its tasks. */
230
+ export async function setDirection(ctx, run, lang, root, r, act, reason) {
231
+ const t = translator(lang);
232
+ if ((await run(["research", act, r.id, ...(reason ? [`--reason=${reason}`] : [])], true)) !== 0)
233
+ return;
234
+ const state = act === "pause" ? "paused" : act === "resume" ? "active" : "done";
235
+ ctx.io.stdout(t(`ui.dirs.${state}.now`, { name: r.name, n: directionTasks(Tree.open(root, ctx.env), r.id) }) + "\n");
197
236
  }
198
237
  /** The person said no to the Strom app: it is not named. */
199
238
  const noApp = (ctx) => stromAppState(ctx.settings) === "no";
@@ -226,7 +226,9 @@ async function archives(ctx, run, lang, shared, root) {
226
226
  const saved = loadLogins(ctx.env);
227
227
  const connectors = listConnectors(shared);
228
228
  const consent = ctx.settings.connectorsConsent();
229
- const agent = ctx.settings.agent(root ? Tree.open(root, ctx.env).config : undefined).value;
229
+ const treeConfig = root ? Tree.open(root, ctx.env).config : undefined;
230
+ const agent = ctx.settings.agent(treeConfig).value;
231
+ const always = ctx.settings.resolve("agent.browser", treeConfig)?.value === "always";
230
232
  const via = (c) => (c.manifest.policy.automation === "manual" ? "manual" : routeOf(ctx.env, c).via);
231
233
  // Each downloader on a line of its own: how its images come, and its login where it can use one.
232
234
  const lines = connectors.map((c) => {
@@ -260,6 +262,20 @@ async function archives(ctx, run, lang, shared, root) {
260
262
  await run(["config", "set", "connectors.consent", consent ? "off" : "on"], true);
261
263
  },
262
264
  },
265
+ // The browser for the agent in every session: Claude Code's alone, shown only when it does the research and is here.
266
+ ...(claudeHere(ctx, treeConfig)
267
+ ? [
268
+ {
269
+ key: "b",
270
+ label: t("ui.archives.agentbrowser", { state: t(always ? "ui.archives.agentbrowser.always" : "ui.archives.agentbrowser.archives") }),
271
+ act: async () => {
272
+ out(t("ui.archives.agentbrowser.about"));
273
+ if (await ctx.confirm(t(always ? "ui.archives.agentbrowser.off" : "ui.archives.agentbrowser.on"), false))
274
+ await run(always ? ["config", "unset", "agent.browser"] : ["config", "set", "agent.browser", "always"], true);
275
+ },
276
+ },
277
+ ]
278
+ : []),
263
279
  ...switchable.map((c) => {
264
280
  const next = via(c) === "browser" ? "direct" : "browser";
265
281
  const name = c.manifest.title ?? c.name;