strom-research 1.17.0-beta.5 → 1.17.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,10 +7,14 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
7
7
 
8
8
  ## 1.17.0
9
9
 
10
- *Připravuje se: bety do 1.17.0-beta.5. Oddíl bude úplný s vydáním.*
10
+ *19. 10. 2026 · patří k aplikaci Strom 3.13.1.*
11
11
 
12
12
  - **Nejdřív, co by hypotézy spojovaly.** Úkoly doplnit, co by spojovaly verze otevřené hypotézy, jdou ve frontě
13
13
  první — i ve stávajícím výzkumu po aktualizaci —, takže aplikace Strom brzy ukáže tyto vazby.
14
+ - **Most aplikace Strom běží, dokud pracuje agent.** Most už neskončí po dvou hodinách klidu, dokud ve stromu
15
+ pracuje agent (i pozastavený běh), takže aplikace Strom vidí výzkum v práci i u stromu, který právě nesleduje.
16
+ Most ukončený pro klid se vrátí na svou adresu, když práce znovu začne; most zastavený `strom live stop` zůstane
17
+ zastavený.
14
18
  - **Přerušené stahování se ohlásí a uklidí.** Stahování, jehož proces skončil uprostřed, se ohlásí jako přerušené,
15
19
  s tím, co se uložilo a co chybí; jeho pracovní složka se uklidí.
16
20
  - **Nový materiál v jazyce výzkumu.** Co přišlo (zaregistrovaný materiál, co přibylo, pravděpodobné duplicity,
@@ -19,7 +23,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
19
23
  1 task, 2 tasks, německy 1 Änderung, 2 Änderungen — místo „úkol(ů)“.
20
24
  - **Víc v jazyce výzkumu.** Osoby bez vlastního záznamu (`strom person list --unproven`, kam odkazuje
21
25
  `strom check`), co zapsal `strom export gedcom`, a varování počítadla ve `strom check` jsou v jazyce výzkumu,
22
- i s počty.
26
+ i s počty – velikost a kvalita obrázků tak, jak je píše jazyk výzkumu (0,4 MB · kvalita běžná).
23
27
  - **Ukládání na Windows počká na antivir.** Soubor, který antivir nebo indexování vyhledávání na chvíli drží, se po
24
28
  krátkém čekání uloží – nebo přečte –, místo aby zápis selhal. Soubor držený déle zastaví příkaz celý, nic z něj
25
29
  nezůstane uložené napůl a hláška říká, že soubor drží jiný program a příkaz jde prostě spustit znovu.
package/CHANGELOG.de.md CHANGED
@@ -7,11 +7,15 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
7
7
 
8
8
  ## 1.17.0
9
9
 
10
- *In Vorbereitung: die Betas bis 1.17.0-beta.5. Der Abschnitt ist mit dem Release vollständig.*
10
+ *19.10.2026 · gehört zur Strom-App 3.13.1.*
11
11
 
12
12
  - **Zuerst, was Hypothesen verbinden würden.** Die Aufgaben, zu ergänzen, was die Varianten einer offenen Hypothese
13
13
  verbinden würden, kommen in der Warteschlange zuerst — auch in einer bestehenden Forschung nach dem Update —, so
14
14
  zeigt die Strom-App diese Verbindungen bald.
15
+ - **Die Brücke der Strom-App läuft, solange ein Agent arbeitet.** Die Brücke endet nicht mehr nach zwei ruhigen
16
+ Stunden, solange ein Agent im Stammbaum arbeitet (auch ein pausierter Lauf), so sieht die Strom-App die Forschung
17
+ bei der Arbeit auch in einem Stammbaum, dem sie gerade nicht folgt. Eine wegen Ruhe beendete Brücke kommt an ihrer
18
+ Adresse zurück, sobald die Arbeit wieder beginnt; eine mit `strom live stop` angehaltene bleibt angehalten.
15
19
  - **Ein unterbrochener Download wird gemeldet und aufgeräumt.** Ein Download, dessen Prozess mittendrin endete, wird
16
20
  als unterbrochen gemeldet, mit dem, was gespeichert ist und was fehlt; sein Arbeitsordner wird entfernt.
17
21
  - **Neues Material in der Sprache der Forschung.** Was hereinkam (registriertes Material, was hinzukam, wahrscheinliche
@@ -20,7 +24,8 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
20
24
  englisch 1 task, 2 tasks, tschechisch 1 úkol, 2 úkoly, 5 úkolů — statt „Änderung(en)“.
21
25
  - **Mehr in der Sprache der Forschung.** Die Personen ohne eigenen Eintrag (`strom person list --unproven`, worauf
22
26
  `strom check` verweist), was `strom export gedcom` geschrieben hat, und eine Zählerwarnung von `strom check` stehen
23
- in der Sprache der Forschung, samt ihren Zahlen.
27
+ in der Sprache der Forschung, samt ihren Zahlen – Größe und Qualität der Bilder so, wie die
28
+ Sprache sie schreibt (0,4 MB · Qualität normal).
24
29
  - **Speichern unter Windows wartet auf den Virenschutz.** Eine Datei, die ein Virenschutz oder die Suchindizierung
25
30
  kurz festhält, wird nach kurzem Warten gespeichert – oder gelesen –, statt dass das Schreiben scheitert. Eine länger
26
31
  festgehaltene Datei hält den Befehl als Ganzes an, nichts davon bleibt halb gespeichert, und die Meldung sagt, dass
package/CHANGELOG.md CHANGED
@@ -7,11 +7,15 @@ The changes of each version in short. The full notes and the install lines are o
7
7
 
8
8
  ## 1.17.0
9
9
 
10
- *In preparation: the betas up to 1.17.0-beta.5. The section is complete with the release.*
10
+ *2026-10-19 · goes with the Strom app 3.13.1.*
11
11
 
12
12
  - **What hypotheses would connect, first.** The tasks to fill in what the variants of an open hypothesis would
13
13
  connect go first in the queue — also in an existing research after the update — so the Strom app can show those
14
14
  links soon.
15
+ - **The Strom app's bridge runs while an agent works.** The bridge no longer ends after two quiet hours while an
16
+ agent works in the tree (a paused run too), so the Strom app sees the research at work even in a tree it does not
17
+ follow. A bridge that ended for quiet comes back at its address when work starts again; one stopped with
18
+ `strom live stop` stays stopped.
15
19
  - **An interrupted download is said and tidied.** A download whose process ended midway is reported as interrupted,
16
20
  with what was saved and what is missing; its work folder is cleared away.
17
21
  - **New material reported in the research's language.** What came in (registered material, what was added, likely
@@ -20,7 +24,8 @@ The changes of each version in short. The full notes and the install lines are o
20
24
  Czech 1 úkol, 2 úkoly, 5 úkolů, in German 1 Änderung, 2 Änderungen — instead of "task(s)".
21
25
  - **More in the research's language.** The people without a record of their own (`strom person list --unproven`,
22
26
  where `strom check` points), what `strom export gedcom` wrote and a counter warning of `strom check` are said in
23
- the research's language, their counts too.
27
+ the research's language, their counts too — the size and quality of the images as the language writes them (in
28
+ Czech 0,4 MB · kvalita běžná).
24
29
  - **Saving on Windows waits for an antivirus.** A file written while an antivirus or the search indexer briefly holds
25
30
  it is saved — or read — after a short wait instead of failing. A file held longer stops the command as a whole,
26
31
  with nothing of it half saved, and the message says another program holds the file and the command can simply run
@@ -778,7 +778,7 @@
778
778
  "ui.app.nolive": "Průběžné sledování výzkumu potřebuje Chrome nebo Edge – Safari webové stránce nedovolí přístup k tomuto počítači.",
779
779
  "ui.app.images": "S obrázky: {n, plural, one {# zápis vyříznutý} few {# zápisy vyříznuté} other {# zápisů vyříznutých}} ze skenů ({mb} MB).",
780
780
  "ui.app.images.unclipped": "{n, plural, one {Další # pramen je na snímcích, ale u něj} few {Další # prameny jsou na snímcích, ale u nich} other {Dalších # pramenů je na snímcích, ale u nich}} zatím nikdo nevyznačil, kde zápis je — agent to umí doplnit.",
781
- "ui.app.images.smaller": "Aby se obrázky vešly do limitu {limit} MB, jsou menší, než je nastaveno: {level} místo {asked}.",
781
+ "ui.app.images.smaller": "Aby se obrázky vešly do limitu {limit} MB, jsou menší, než je nastaveno: kvalita {level}, nastavena {asked}.",
782
782
  "ui.app.images.left": "{n, plural, one {# zápis nejdál od výzkumu je} few {# zápisy nejdál od výzkumu jsou} other {# zápisů nejdál od výzkumu je}} bez obrázku, aby se vše vešlo do limitu {limit} MB.",
783
783
  "ui.app.images.scope": "{n, plural, one {# zápis lidí vzdálenějších od výzkumu je} few {# zápisy lidí vzdálenějších od výzkumu jsou} other {# zápisů lidí vzdálenějších od výzkumu je}} bez obrázku (nastavení excerpts.for: {for}).",
784
784
  "ui.doc.installed": "strom {version} na vlastním Node {node} (oficiálním, z nodejs.org)",
@@ -1305,7 +1305,11 @@
1305
1305
  "ui.export.valid.warnings": " platný GEDCOM 5.5.1 ({n} varování)",
1306
1306
  "ui.export.committed": "zapsáno do historie",
1307
1307
  "ui.export.images": " {excerpts, plural, one {# výřez} few {# výřezy} other {# výřezů}} z {sources, plural, one {# pramene} other {# pramenů}}, {size} · kvalita {level}",
1308
- "ui.export.images.smaller": " POZOR: zmenšeno, aby se vešlo do limitu {mb} MB: {level} místo {asked} — víc místa: --images-max-mb <n> (strom config set excerpts.mb <n>)",
1308
+ "ui.export.quality.small": "nízká",
1309
+ "ui.export.quality.normal": "běžná",
1310
+ "ui.export.quality.sharp": "vysoká",
1311
+ "ui.export.quality.smaller": "nejnižší",
1312
+ "ui.export.images.smaller": " POZOR: zmenšeno, aby se vešlo do limitu {mb} MB: kvalita {level}, nastavena {asked} — víc místa: --images-max-mb <n> (strom config set excerpts.mb <n>)",
1309
1313
  "ui.export.images.dropped": " POZOR: vynecháno, aby se vešlo do limitu {mb} MB, nejdřív nejvzdálenější od výzkumu: {n, plural, one {# pramen} few {# prameny} other {# pramenů}} ({ids}) — víc místa: --images-max-mb <n>",
1310
1314
  "ui.export.images.scope": " prameny lidí vzdálenějších od výzkumu, než říká excerpts.for={for}: {n} — všechny: --images-for all",
1311
1315
  "ui.export.images.missing": " snímek není na tomto počítači: {ids}",
@@ -1305,6 +1305,10 @@
1305
1305
  "ui.export.valid.warnings": " gültiges GEDCOM 5.5.1 ({n, plural, one {# Warnung} other {# Warnungen}})",
1306
1306
  "ui.export.committed": "in die Geschichte geschrieben",
1307
1307
  "ui.export.images": " {excerpts, plural, one {# Ausschnitt} other {# Ausschnitte}} aus {sources, plural, one {# Quelle} other {# Quellen}}, {size} · Qualität {level}",
1308
+ "ui.export.quality.small": "niedrig",
1309
+ "ui.export.quality.normal": "normal",
1310
+ "ui.export.quality.sharp": "hoch",
1311
+ "ui.export.quality.smaller": "sehr niedrig",
1308
1312
  "ui.export.images.smaller": " ACHTUNG: verkleinert, damit es in die Grenze von {mb} MB passt: {level} statt {asked} — mehr Platz: --images-max-mb <n> (strom config set excerpts.mb <n>)",
1309
1313
  "ui.export.images.dropped": " ACHTUNG: ausgelassen, damit es in die Grenze von {mb} MB passt, das von der Forschung am weitesten Entfernte zuerst: {n, plural, one {# Quelle} other {# Quellen}} ({ids}) — mehr Platz: --images-max-mb <n>",
1310
1314
  "ui.export.images.scope": " {n, plural, one {# Quelle} other {# Quellen}} von Personen, die der Forschung ferner stehen als excerpts.for={for} — alle: --images-for all",
package/dist/cli/ui.js CHANGED
@@ -1218,6 +1218,10 @@ export const UI = {
1218
1218
  "ui.export.valid.warnings": " valid GEDCOM 5.5.1 ({n, plural, one {# warning} other {# warnings}})",
1219
1219
  "ui.export.committed": "committed",
1220
1220
  "ui.export.images": " {excerpts, plural, one {# excerpt} other {# excerpts}} of {sources, plural, one {# source} other {# sources}}, {size} · quality {level}",
1221
+ "ui.export.quality.small": "small",
1222
+ "ui.export.quality.normal": "normal",
1223
+ "ui.export.quality.sharp": "sharp",
1224
+ "ui.export.quality.smaller": "smaller",
1221
1225
  "ui.export.images.smaller": " WARNING made smaller to fit the limit of {mb} MB: {level} instead of {asked} — more room: --images-max-mb <n> (strom config set excerpts.mb <n>)",
1222
1226
  "ui.export.images.dropped": " WARNING left out to fit the limit of {mb} MB, the farthest from the research first: {n, plural, one {# source} other {# sources}} ({ids}) — more room: --images-max-mb <n>",
1223
1227
  "ui.export.images.scope": " {n, plural, one {# source} other {# sources}} of people farther from the research than excerpts.for={for} — all of them: --images-for all",
@@ -2,7 +2,7 @@
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { register } from "../cli/registry.js";
5
- import { counted, lines, table } from "../cli/format.js";
5
+ import { counted, lines, numberIn, table } from "../cli/format.js";
6
6
  import { exportGedcom, GED_PROFILES } from "../gedcom/export.js";
7
7
  import { hasGedErrors, validateGedcom } from "../gedcom/validate.js";
8
8
  import { writeFileAtomic } from "../core/json.js";
@@ -101,14 +101,18 @@ export function writeTreeGed(tree, opts) {
101
101
  }
102
102
  return { ...result, findings, file, for: opts.for, ...(images && set ? { images: images.report, imageSettings: set } : {}) };
103
103
  }
104
- const mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
104
+ /** A size of images as the language writes it (0,4 MB in Czech, 0.4 MB in English). */
105
+ const mbIn = (bytes, lang) => `${numberIn(lang, bytes / 1024 / 1024, { minimumFractionDigits: 1, maximumFractionDigits: 1 })} MB`;
106
+ /** A quality of the images (excerpt.ts LEVELS) as the language says it; in English its name, the value excerpts.quality takes. */
107
+ const QUALITY_WORDS = { sharp: "ui.export.quality.sharp", normal: "ui.export.quality.normal", small: "ui.export.quality.small", smaller: "ui.export.quality.smaller" };
108
+ export const qualityIn = (q, lang) => (Object.hasOwn(QUALITY_WORDS, q) ? ui(lang, QUALITY_WORDS[q]) : q);
105
109
  /**
106
110
  * What an export with images took in and left out, and what to do about it — what strom decided itself is said as a
107
111
  * warning; in `lang` (a person's research language, else English).
108
112
  */
109
113
  export function imagesLines(r, images, lang = "en") {
110
114
  const few = (ids) => `${ids.slice(0, 8).join(", ")}${ids.length > 8 ? ` … (${ids.length})` : ""}`;
111
- return lines(ui(lang, "ui.export.images", { excerpts: r.excerpts, sources: r.sources, size: mb(r.bytes), level: r.level }), r.level !== r.asked ? ui(lang, "ui.export.images.smaller", { mb: images.mb, level: r.level, asked: r.asked }) : undefined, r.dropped.length ? ui(lang, "ui.export.images.dropped", { mb: images.mb, n: r.dropped.length, ids: few(r.dropped) }) : undefined, r.outOfScope ? ui(lang, "ui.export.images.scope", { n: r.outOfScope, for: images.for }) : undefined, r.missing.length ? ui(lang, "ui.export.images.missing", { ids: few(r.missing) }) : undefined, r.unclipped.length ? ui(lang, "ui.export.images.unclipped", { ids: few(r.unclipped) }) : undefined, r.later.length ? ui(lang, "ui.export.images.later", { n: r.later.length }) : undefined);
115
+ return lines(ui(lang, "ui.export.images", { excerpts: r.excerpts, sources: r.sources, size: mbIn(r.bytes, lang), level: qualityIn(r.level, lang) }), r.level !== r.asked ? ui(lang, "ui.export.images.smaller", { mb: numberIn(lang, images.mb, { maximumFractionDigits: 3, useGrouping: false }), level: qualityIn(r.level, lang), asked: qualityIn(r.asked, lang) }) : undefined, r.dropped.length ? ui(lang, "ui.export.images.dropped", { mb: numberIn(lang, images.mb, { maximumFractionDigits: 3, useGrouping: false }), n: r.dropped.length, ids: few(r.dropped) }) : undefined, r.outOfScope ? ui(lang, "ui.export.images.scope", { n: r.outOfScope, for: images.for }) : undefined, r.missing.length ? ui(lang, "ui.export.images.missing", { ids: few(r.missing) }) : undefined, r.unclipped.length ? ui(lang, "ui.export.images.unclipped", { ids: few(r.unclipped) }) : undefined, r.later.length ? ui(lang, "ui.export.images.later", { n: r.later.length }) : undefined);
112
116
  }
113
117
  /** The images of an export: the settings, each overridden by its option. */
114
118
  export function imageSettings(ctx, tree, opts) {
@@ -136,7 +136,7 @@ register({
136
136
  if (task && !["open", "doing", "parked"].includes(task.state))
137
137
  throw new UsageError(`${task.id} is ${task.state}`);
138
138
  const s = startSession(tree, { ...(task ? { task } : {}), ...(research ? { research: research.id } : {}) });
139
- reviveLive(tree.root, ctx.env);
139
+ reviveLive(tree.root, ctx.env, { work: true });
140
140
  const shared = ctx.settings.shared()?.value;
141
141
  const brief = buildBrief(tree, { ...(task ? { task } : {}), session: s, budget: ctx.settings.number("brief.budget", tree.config, DEFAULT_BUDGET), shared, browser: briefBrowser(tree, shared, s, task) });
142
142
  // a task the queue holds back, started by its ID: the agent hears why before it begins
@@ -947,8 +947,8 @@ register({
947
947
  }
948
948
  gateTrail.length = 0;
949
949
  }
950
- // the Strom app followed the research and its bridge ended without a word: on its address again
951
- reviveLive(root, ctx.env);
950
+ // the Strom app followed the research and its bridge ended without a word, or idle: on its address again
951
+ reviveLive(root, ctx.env, { work: true });
952
952
  // The Strom app follows it from the first session on — not while the run still waits for its gate.
953
953
  if (opts.follow && !followed) {
954
954
  followed = true;
@@ -6,7 +6,7 @@ import path from "node:path";
6
6
  import { configDir, userHome } from "../core/paths.js";
7
7
  import { prependPath, spawnAgent } from "../runners/runner.js";
8
8
  import { register } from "../cli/registry.js";
9
- import { lines, truncate } from "../cli/format.js";
9
+ import { lines, numberIn, truncate } from "../cli/format.js";
10
10
  import { ui } from "../cli/ui.js";
11
11
  import { StromError, UsageError } from "../core/errors.js";
12
12
  import { PROFILES } from "../agents/profiles.js";
@@ -37,7 +37,7 @@ import { commitNow, shimDir } from "./session.js";
37
37
  import { abandonedSessions, closeSession, openSessions } from "../core/session.js";
38
38
  import { enterWorker, liveWorkers } from "../core/workers.js";
39
39
  import { phrase } from "../core/phrases.js";
40
- import { imageSettings, writeGedcoms, writeTreeGed } from "./output.js";
40
+ import { imageSettings, qualityIn, writeGedcoms, writeTreeGed } from "./output.js";
41
41
  import { shortcutName } from "../cli/wizard.js";
42
42
  import { refuseInArchive } from "../core/state.js";
43
43
  /** Has the user trusted this folder in Claude Code already? (Its own record, read only.) */
@@ -293,7 +293,7 @@ register({
293
293
  const r = ged.images;
294
294
  const set = ged.imageSettings;
295
295
  const withImages = r && set
296
- ? lines(ui(lang, "ui.app.images", { n: String(r.excerpts), mb: (r.bytes / 1024 / 1024).toFixed(1) }), r.level !== r.asked ? ui(lang, "ui.app.images.smaller", { limit: String(set.mb), level: r.level, asked: r.asked }) : undefined, r.dropped.length ? ui(lang, "ui.app.images.left", { n: String(r.dropped.length), limit: String(set.mb) }) : undefined, r.outOfScope ? ui(lang, "ui.app.images.scope", { n: String(r.outOfScope), for: set.for }) : undefined, r.unclipped.length ? ui(lang, "ui.app.images.unclipped", { n: String(r.unclipped.length) }) : undefined)
296
+ ? lines(ui(lang, "ui.app.images", { n: String(r.excerpts), mb: numberIn(lang, r.bytes / 1024 / 1024, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) }), r.level !== r.asked ? ui(lang, "ui.app.images.smaller", { limit: numberIn(lang, set.mb, { maximumFractionDigits: 3, useGrouping: false }), level: qualityIn(r.level, lang), asked: qualityIn(r.asked, lang) }) : undefined, r.dropped.length ? ui(lang, "ui.app.images.left", { n: String(r.dropped.length), limit: numberIn(lang, set.mb, { maximumFractionDigits: 3, useGrouping: false }) }) : undefined, r.outOfScope ? ui(lang, "ui.app.images.scope", { n: String(r.outOfScope), for: set.for }) : undefined, r.unclipped.length ? ui(lang, "ui.app.images.unclipped", { n: String(r.unclipped.length) }) : undefined)
297
297
  : undefined;
298
298
  // B, D: the app reaches strom on this computer — the app installed from a browser first (that of the browser
299
299
  // its tree came from, app.browser, when several), else a tab of that browser, else of the default one when the
@@ -361,11 +361,13 @@ register({
361
361
  tree: true,
362
362
  description: "A small web server on this computer only (127.0.0.1, a secret address), that only reads: the Strom app, opened with\n" +
363
363
  "strom app --live, takes the tree from it and hears what changes — who is at work on what, what was recorded, what\n" +
364
- "waits for the user. It ends by itself when nobody asks it anything for two hours, or with strom live stop.\n" +
364
+ "waits for the user. It ends by itself when nobody asks it anything for two hours while nobody works in the tree,\n" +
365
+ "or with strom live stop.\n" +
365
366
  "One error does not end it (a read that fails is tried again); what it did and what went wrong is in .strom/live.log.\n" +
366
367
  "Started again by itself (strom updated, one that ended without a word or when idle) it takes the address it had\n" +
367
- "(its port while free), so the app following it goes on by itself; one that ended without a word is started again\n" +
368
- "when a session starts. Stopped with strom live stop, the next one gets a new secret address.",
368
+ "(its port while free), so the app following it goes on by itself; one that ended without a word or when idle is\n" +
369
+ "started again when a session starts (idle: only one the app was given, never in an archive). Stopped with strom\n" +
370
+ "live stop, the next one gets a new secret address.",
369
371
  examples: ["strom live", "strom live start", "strom live stop"],
370
372
  run(ctx) {
371
373
  const tree = bridgeTree(ctx);
package/dist/core/live.js CHANGED
@@ -4,7 +4,8 @@
4
4
  // ?live=<address>, takes the tree's GEDCOM from it and hears what changes —
5
5
  // who is at work on what, what was recorded, what waits for the user. Nothing
6
6
  // leaves the computer. The bridge ends by itself when nobody has asked it
7
- // anything for a while (LIVE_IDLE_MS), or with strom live stop.
7
+ // anything for a while (LIVE_IDLE_MS) and nobody works in the tree, or with
8
+ // strom live stop.
8
9
  //
9
10
  // GET <token>/status the tree, its researches, who is at work, what waits; what the research takes from
10
11
  // the app (accepts). ?poll=1: the app asking now and then — the bridge's idle time goes on
@@ -57,7 +58,8 @@
57
58
  //
58
59
  // The secret: started again by itself — a newer strom on disk (live start
59
60
  // --current), one that ended without a word (reviveLive), the first run of a
60
- // newer strom bringing its bridges back, a start after it ended when idle — it
61
+ // newer strom bringing its bridges back, a start after it ended when idle (a new
62
+ // session too, when its secret is kept and the tree is no archive) — it
61
63
  // takes the address it had (.strom/live-last.json: its port while free, its
62
64
  // token), so the app following it goes on by itself. Ended for good (strom live
63
65
  // stop, strom uninstall) its secret is dropped: the next bridge gets a new one
@@ -86,7 +88,7 @@ import { phrase } from "./phrases.js";
86
88
  import { exportGedcom } from "../gedcom/export.js";
87
89
  import { excerptSettings, planExcerpts } from "./excerpt.js";
88
90
  import { adoptedAt, adoptedEmpty, adoptionWait, noteAdoptAsked, markSentAgain, undoneSend, undoneSince, failReceived, inboxTrees, markAdopted, noteAdoptFailed, noteNothingSent, pendingAdoption, receiveAdopted, receivedAll, receivedPending, receiveTree, recentSends, stampAppVersion, syncConflicts, SYNC_INBOX, SYNC_MAX_BYTES, namesOf } from "./sync.js";
89
- import { modeOf, settleArchive } from "./mode.js";
91
+ import { isArchive, modeOf, settleArchive } from "./mode.js";
90
92
  import { actsAsArchive, stateOf } from "./state.js";
91
93
  import { raiseHypothesisLinkTasks, settleHypothesisLinks } from "./hypolinks.js";
92
94
  import { PROFILES } from "../agents/profiles.js";
@@ -98,7 +100,7 @@ import { checkOriginalMeta, freeBytes, knownOriginal, materialWaiting, originalM
98
100
  import { BATCH_ID, batchEstimate, batchLimits, batchRoom, batchPath, batchStatus, idleBatches, noteBatch, openBatch, readBatch } from "./batches.js";
99
101
  /** New images for the app, made for at most so long when the tree changed (the rest the next time). */
100
102
  const LIVE_IMAGES_MS = 20_000;
101
- import { liveWorkers } from "./workers.js";
103
+ import { anyoneAtWork, liveWorkers } from "./workers.js";
102
104
  import { monthSpend, openSessions } from "./session.js";
103
105
  import { rankTasks } from "./queue.js";
104
106
  import { humanTask } from "../cli/human.js";
@@ -570,16 +572,37 @@ export function stopLive(root, why = "strom live stop", opts = {}) {
570
572
  // no bridge of that number ran (it ended meanwhile, the number another program's): none was stopped
571
573
  return how === "gone" ? "none" : how;
572
574
  }
575
+ /** Whether a bridge's end was its idle time running out (the reason it wrote: "idle: …"). */
576
+ function endedIdle(last) {
577
+ return /^idle\b/.test(last.ended?.reason ?? "");
578
+ }
573
579
  /**
574
580
  * The bridge of a tree that ended without a word (a crash, killed) is started again — on its address, so the
575
- * Strom app following it goes on by itself. One that ended as it should (idle, stopped) stays so.
581
+ * Strom app following it goes on by itself. One that ended as it should (idle, stopped) stays so — but new work in the
582
+ * tree (work: a session starts) brings back one that ended idle, at its address, when the app was given it (its secret
583
+ * kept: never after strom live stop or uninstall) and the tree is no archive: the app watching the tree without
584
+ * following it (/status?poll=1) sees it at work again.
576
585
  */
577
- export function reviveLive(root, env) {
586
+ export function reviveLive(root, env, opts = {}) {
578
587
  const last = readLast(root);
579
- if (!last || last.ended || alive(last.pid) || liveRunning(root))
588
+ if (!last || alive(last.pid) || liveRunning(root))
580
589
  return undefined;
581
- const info = startLive(root, env, { why: `the bridge ${last.pid} ended without a word: started again` });
582
- // one that does not come up is not tried at every session again
590
+ if (last.ended) {
591
+ if (!opts.work || !last.token || !endedIdle(last))
592
+ return undefined;
593
+ try {
594
+ if (isArchive({ config: readJsonIfExists(path.join(root, TREE_FILE)) ?? {} }))
595
+ return undefined;
596
+ }
597
+ catch {
598
+ return undefined;
599
+ }
600
+ }
601
+ const why = last.ended
602
+ ? `work in the tree again: the bridge ${last.pid} that ended idle started again at its address`
603
+ : `the bridge ${last.pid} ended without a word: started again`;
604
+ const info = startLive(root, env, { why });
605
+ // one that does not come up is not tried at every session again (each try waits for it some seconds)
583
606
  if (!info)
584
607
  writeLast(root, { ...last, ended: { at: new Date().toISOString(), reason: "did not start again" } });
585
608
  return info;
@@ -2610,7 +2633,9 @@ export function serveLive(root, env) {
2610
2633
  tidied = Date.now();
2611
2634
  safe("keeping order", () => autoTidy(Tree.open(root, env)));
2612
2635
  }
2613
- if (streams.size)
2636
+ // the idle time runs only while nobody works in the tree (a run waiting for its gate works too): the app watching
2637
+ // the tree without following it (/status?poll=1) still finds the bridge while an agent is at work
2638
+ if (streams.size || anyoneAtWork(root))
2614
2639
  lastAsked = Date.now();
2615
2640
  if (Date.now() - lastAsked > idleMs)
2616
2641
  return stop("idle: nobody asked for a while", "idle", false);
package/dist/core/tree.js CHANGED
@@ -27,7 +27,7 @@ import { lastBackup } from "./backup.js";
27
27
  export const TREE_FILE = "strom.json";
28
28
  // between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
29
29
  // at a release the release's own (package.json)
30
- export const VERSION = "1.17.0-beta.5";
30
+ export const VERSION = "1.17.0";
31
31
  const GITIGNORE = `# Strom Research — local state and large files are not versioned
32
32
  .strom/
33
33
  /media/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strom-research",
3
- "version": "1.17.0-beta.5",
3
+ "version": "1.17.0",
4
4
  "description": "Genealogical research toolkit for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,8 +57,5 @@
57
57
  "devDependencies": {
58
58
  "@types/node": "^22.20.4",
59
59
  "typescript": "^7.0.2"
60
- },
61
- "publishConfig": {
62
- "tag": "beta"
63
60
  }
64
61
  }