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.
- package/README.md +3 -0
- package/assets/lang/cs.json +117 -2
- package/assets/lang/de.json +117 -2
- package/assets/method/narrate.md +5 -1
- package/assets/plugins/gates/claude-usage/gate.ts +2 -1
- package/dist/agents/files.js +6 -4
- package/dist/cli/main.js +21 -3
- package/dist/cli/menu-links.js +35 -1
- package/dist/cli/menu-research.js +41 -2
- package/dist/cli/menu-settings.js +17 -1
- package/dist/cli/menu.js +19 -8
- package/dist/cli/ui.js +57 -1
- package/dist/commands/guide.js +6 -1
- package/dist/commands/links.js +39 -3
- package/dist/commands/pack.js +21 -18
- package/dist/commands/research.js +42 -1
- package/dist/commands/session.js +79 -9
- package/dist/commands/setup.js +3 -0
- package/dist/commands/start.js +6 -3
- package/dist/commands/story.js +47 -6
- package/dist/commands/tasks.js +15 -6
- package/dist/commands/trees.js +82 -2
- package/dist/core/changelog.js +223 -0
- package/dist/core/clock.js +20 -0
- package/dist/core/config.js +2 -0
- package/dist/core/connector.js +25 -1
- package/dist/core/directions.js +41 -0
- package/dist/core/links.js +17 -4
- package/dist/core/live.js +393 -50
- package/dist/core/pack.js +31 -0
- package/dist/core/phrases.js +61 -0
- package/dist/core/queue.js +7 -2
- package/dist/core/relocate.js +4 -4
- package/dist/core/session.js +8 -2
- package/dist/core/trash.js +72 -0
- package/dist/core/tree.js +1 -1
- package/dist/core/workers.js +28 -3
- package/dist/runners/claude.js +2 -1
- package/dist/runners/jsonl.js +2 -2
- package/dist/runners/runner.js +16 -3
- package/dist/runners/script.js +2 -1
- package/package.json +1 -1
package/dist/cli/menu.js
CHANGED
|
@@ -203,8 +203,11 @@ export async function runMenu(ctx, run) {
|
|
|
203
203
|
ctx.settings.config.stromAppFollow = follow ? "yes" : "no";
|
|
204
204
|
ctx.settings.save();
|
|
205
205
|
}
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
// opened when the first session starts: a run may wait for its gate first
|
|
207
|
+
if (follow) {
|
|
208
|
+
args.push("--follow");
|
|
209
|
+
out(t("ui.run.follow.later"));
|
|
210
|
+
}
|
|
208
211
|
}
|
|
209
212
|
out(t("ui.run.start"));
|
|
210
213
|
await run(["run", ...args]);
|
|
@@ -246,13 +249,15 @@ export async function runMenu(ctx, run) {
|
|
|
246
249
|
await run(["chat", "--agent", others[i].id]);
|
|
247
250
|
},
|
|
248
251
|
};
|
|
252
|
+
// Another tree, right after the research's own items.
|
|
253
|
+
items.push({ key: "6", label: known.length > 1 ? t("ui.menu.trees") : t("ui.menu.newtree"), act: async () => pickTree(ctx, run, lang) });
|
|
249
254
|
// The Strom app: open the research in it (live while an agent is at work); not known yet: what it is, and whether they want it.
|
|
250
255
|
const app = stromAppState(ctx.settings);
|
|
251
256
|
if (app !== "no") {
|
|
252
257
|
const atWork = liveWorkers(root).length > 0 || runsAtWork(root).length > 0;
|
|
253
258
|
const hasApp = app !== "unknown" || Boolean(installedStromApp(ctx.env, process.platform, stromAppUrl(ctx.settings)));
|
|
254
259
|
appItem = {
|
|
255
|
-
key: "
|
|
260
|
+
key: "7",
|
|
256
261
|
label: t(!hasApp ? "ui.menu.app.new" : atWork ? "ui.menu.app.watch" : "ui.menu.app"),
|
|
257
262
|
act: async () => openStromApp(ctx, run, lang, hasApp),
|
|
258
263
|
};
|
|
@@ -264,12 +269,9 @@ export async function runMenu(ctx, run) {
|
|
|
264
269
|
// Several trees and none chosen: pick one first.
|
|
265
270
|
items.push({ key: "1", label: t("ui.menu.trees"), act: async () => pickTree(ctx, run, lang) });
|
|
266
271
|
}
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
const settings = { key: "6", label: t("ui.menu.settings"), act: async () => settingsMenu(ctx, run, lang, root, newer) };
|
|
272
|
+
// The settings of this computer (with the check); at most nine: the newer strom is taken in the settings, the line above says where.
|
|
273
|
+
const settings = { key: "8", label: t("ui.menu.settings"), act: async () => settingsMenu(ctx, run, lang, root, newer) };
|
|
270
274
|
items.push(settings);
|
|
271
|
-
if (root)
|
|
272
|
-
items.push({ key: "8", label: known.length > 1 ? t("ui.menu.trees") : t("ui.menu.newtree"), act: async () => pickTree(ctx, run, lang) });
|
|
273
275
|
// Only with several agents here: last, so that it moves no other number.
|
|
274
276
|
if (other)
|
|
275
277
|
items.push(other);
|
|
@@ -428,6 +430,7 @@ async function pickTree(ctx, run, lang) {
|
|
|
428
430
|
// handing a research over: this one packed, one someone sent unpacked
|
|
429
431
|
const packAt = at >= 0 ? options.push({ label: ui(lang, "ui.trees.pack", { name: known[at].name }) }) - 1 : -1;
|
|
430
432
|
const unpackAt = options.push({ label: ui(lang, "ui.trees.unpack") }) - 1;
|
|
433
|
+
const removeAt = known.length ? options.push({ label: ui(lang, "ui.trees.remove") }) - 1 : -1;
|
|
431
434
|
const back = at >= 0 ? { back: ui(lang, "ui.back.stay", { name: known[at].name }) } : {};
|
|
432
435
|
const i = await ctx.choose(ui(lang, "ui.trees.pick"), options, Math.max(0, at), back);
|
|
433
436
|
if (i === undefined || i === at)
|
|
@@ -447,6 +450,14 @@ async function pickTree(ctx, run, lang) {
|
|
|
447
450
|
await partsPause(ctx, lang);
|
|
448
451
|
return;
|
|
449
452
|
}
|
|
453
|
+
if (i === removeAt) {
|
|
454
|
+
const which = await ctx.choose(ui(lang, "ui.remove.which"), known.map((k) => ({ label: `${k.name} (${ctx.display(k.root)})` })), known.length, { back: ui(lang, "ui.browse.back") }); // Enter goes back
|
|
455
|
+
if (which === undefined)
|
|
456
|
+
return;
|
|
457
|
+
await run(["trees", "remove", known[which].root]);
|
|
458
|
+
await partsPause(ctx, lang);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
450
461
|
if (i === unpackAt) {
|
|
451
462
|
const answer = (await ctx.ask(ui(lang, "ui.unpack.file"))).trim();
|
|
452
463
|
if (!answer || answer === "0")
|
package/dist/cli/ui.js
CHANGED
|
@@ -104,6 +104,7 @@ export const UI = {
|
|
|
104
104
|
"ui.menu.app.live": "The agent is at work — you can watch it live in the Strom app: choice {key}.",
|
|
105
105
|
"ui.app.open.now": "Once it is installed: open the research in it?",
|
|
106
106
|
"ui.run.follow": "Watch the work live in the Strom app meanwhile?",
|
|
107
|
+
"ui.run.follow.later": "The Strom app opens when the agent begins its first session.",
|
|
107
108
|
"ui.app.opened": "Opening the Strom app ({where}).",
|
|
108
109
|
"ui.app.browser": "in your browser",
|
|
109
110
|
"ui.app.url": "Open it here: {url}",
|
|
@@ -162,6 +163,7 @@ export const UI = {
|
|
|
162
163
|
"ui.run.stop.resets": " (resets {at})",
|
|
163
164
|
"ui.run.stop.auth": "the agent is not logged in — run {command} once and log in",
|
|
164
165
|
"ui.run.stop.failed": "the agent failed: {detail}",
|
|
166
|
+
"ui.run.finishing": "■ Ctrl-C: session {session} is asked to finish — the agent writes down what it found and closes it, then the run stops (no next session). Ctrl-C again stops it now.",
|
|
165
167
|
"ui.run.stopping": "■ stopping — the session is closed and its task goes back to the queue; Ctrl-C again to quit at once",
|
|
166
168
|
"ui.run.gone": "· {session} closed — its run had stopped{back}",
|
|
167
169
|
"ui.run.back": "; {task} is back in the queue",
|
|
@@ -515,6 +517,14 @@ export const UI = {
|
|
|
515
517
|
"ui.consent.gate.set": "Let the condition “{name}” decide when the agent working alone goes on?",
|
|
516
518
|
"ui.consent.gate.unset": "Remove the condition “{name}” — the agent working alone no longer asks it?",
|
|
517
519
|
"ui.consent.gate.skip": "Let the agent work on its own without the condition “{name}”?",
|
|
520
|
+
"ui.run.browser.always": "· the browser (Claude in Chrome) in every session, as you set it (agent.browser always) — without asking for {hosts}; Chrome with the Claude extension must be running",
|
|
521
|
+
"ui.consent.browser": "Give the agent browser tools (Claude in Chrome) in every research session? It works in your own Chrome — where you are signed in — for the sites of the research's archives without asking; in a conversation it asks about any other site.",
|
|
522
|
+
"ui.archives.agentbrowser": "The browser for the agent (Claude in Chrome): {state}",
|
|
523
|
+
"ui.archives.agentbrowser.archives": "only for archives through the browser",
|
|
524
|
+
"ui.archives.agentbrowser.always": "in every session",
|
|
525
|
+
"ui.archives.agentbrowser.about": "In every session, the agent can work in your own Chrome — where you are signed in — so you can ask it for anything through the browser. The sites of the research's archives without asking; in a conversation it asks about any other site, working alone it uses none other. Chrome with the Claude extension must be running.",
|
|
526
|
+
"ui.archives.agentbrowser.on": "Give the agent the browser in every session?",
|
|
527
|
+
"ui.archives.agentbrowser.off": "Back to the browser only for archives through the browser?",
|
|
518
528
|
"ui.consent.remote": "Start the Claude Code sessions strom opens with Remote Control, so that you can follow and steer them from claude.ai or the Claude app on your phone?",
|
|
519
529
|
"ui.consent.hook.on": "Let the hook “{name}” be told of everything saved into your researches? It is a program on this computer and does with it what it was written to do.",
|
|
520
530
|
"ui.consent.hook.off": "Stop telling the hook “{name}” what is saved?",
|
|
@@ -546,6 +556,16 @@ export const UI = {
|
|
|
546
556
|
"ui.link.what.chat.person": "a conversation with the agent ({agent}) about {person}.",
|
|
547
557
|
"ui.link.chat.say": "Let us look at {person}: tell me briefly what the research knows of this person, and ask me what I want to find out.",
|
|
548
558
|
"ui.link.what.review.person": "look at {person} again.",
|
|
559
|
+
"ui.link.what.direction.pause": "pause the direction “{name}”: its tasks ({n}) wait out of the queue and nothing new is proposed for it. Nothing is deleted.",
|
|
560
|
+
"ui.link.what.direction.done": "end the direction “{name}”: its tasks ({n}) leave the queue. Nothing is deleted, it can be taken up again.",
|
|
561
|
+
"ui.link.what.direction.resume": "take the direction “{name}” up again: its tasks ({n}) come back to the queue and the research proposes what is missing.",
|
|
562
|
+
"ui.link.direction.none": "Direction {id} is not in this research. Nothing was done.",
|
|
563
|
+
"ui.link.direction.cannot": "The direction “{name}” is {state} already. Nothing was done.",
|
|
564
|
+
"ui.link.direction.why": "Why? (a few words, kept with the direction; Enter: no reason)",
|
|
565
|
+
"ui.link.what.chat.research": "a conversation with the agent ({agent}) about the direction “{name}”, only its tasks.",
|
|
566
|
+
"ui.link.chat.research.say": "Let us work on the direction “{name}” ({id}), on its tasks.",
|
|
567
|
+
"ui.link.what.finish": "ask {agent} to finish session {session} ({task}): it writes down what it found and closes the session; working alone, it starts no next one.",
|
|
568
|
+
"ui.link.finish.none": "Session {session} is not at work (it has closed meanwhile). Nothing was done.",
|
|
549
569
|
"ui.link.what.review.family": "look at {person} again, with the partners and children.",
|
|
550
570
|
"ui.link.what.review.line": "look at {person} again, with the ancestors.",
|
|
551
571
|
"ui.link.what.research.ancestors": "search for the ancestors of {person} (a new direction of the research).",
|
|
@@ -635,7 +655,7 @@ export const UI = {
|
|
|
635
655
|
"ui.research.knows": "What do you know of it? Dates, places, stories of the family help the agent (Enter: nothing)",
|
|
636
656
|
"ui.research.done": "✓ The research “{name}” is started.",
|
|
637
657
|
"ui.research.chat": "Start on it with the agent now?",
|
|
638
|
-
"ui.research.say": "I started a new research, “{name}” — let us begin with it.",
|
|
658
|
+
"ui.research.say": "I started a new research, “{name}” ({id}) — let us begin with it, with its tasks.",
|
|
639
659
|
"ui.settings.title": "Settings (on this computer)",
|
|
640
660
|
"ui.settings.wizard": "Language, agent, model, what the agent may do alone, stories… (the setup)",
|
|
641
661
|
"ui.settings.alone": "The agent working alone: {minutes} min a task · condition: {gate}",
|
|
@@ -697,6 +717,20 @@ export const UI = {
|
|
|
697
717
|
"ui.intake.empty": "There is nothing in it to take in.",
|
|
698
718
|
"ui.intake.ours": "{path} is strom's own folder (the research itself), not material of the family.",
|
|
699
719
|
"ui.research.exists": "This research is there already: “{name}” — the agent goes on with it.",
|
|
720
|
+
"ui.more.directions": "The directions of the research: pause, end or take one up again",
|
|
721
|
+
"ui.dirs.pick": "Which direction?",
|
|
722
|
+
"ui.dirs.item": "{name} — {state} · tasks: {n}",
|
|
723
|
+
"ui.dirs.active": "going on",
|
|
724
|
+
"ui.dirs.paused": "paused",
|
|
725
|
+
"ui.dirs.done": "ended",
|
|
726
|
+
"ui.dirs.what": "“{name}”:",
|
|
727
|
+
"ui.dirs.do.pause": "Pause it — its tasks wait, the agent takes others",
|
|
728
|
+
"ui.dirs.do.done": "End it — its tasks leave the queue (nothing is deleted)",
|
|
729
|
+
"ui.dirs.do.resume": "Take it up again — its tasks back in the queue",
|
|
730
|
+
"ui.dirs.paused.now": "⏸ “{name}” is paused: its tasks ({n}) wait until you take it up again (here).",
|
|
731
|
+
"ui.dirs.done.now": "■ “{name}” is ended: its tasks ({n}) are out of the queue — nothing is deleted, it can be taken up again here.",
|
|
732
|
+
"ui.dirs.active.now": "▶ “{name}” goes on: its tasks ({n}) are back in the queue.",
|
|
733
|
+
"ui.research.resume": "This research is there already, {state}: “{name}”. Take it up again?",
|
|
700
734
|
"ui.alone.gate.bad": "Please a number of points from 0 to 100 (e.g. 10).",
|
|
701
735
|
"ui.waiting.long": "That is long for an answer here (at most {n} characters) — a link or a sentence; the rest tell the agent in a conversation.",
|
|
702
736
|
"ui.settings.remote.chats": "conversations yes, working alone no",
|
|
@@ -771,6 +805,28 @@ export const UI = {
|
|
|
771
805
|
"ui.unpack.person": "only the person at the computer unpacks a research (it adds connectors)",
|
|
772
806
|
"ui.pack.launch.extract": "First unpack the whole ZIP file - on Windows: right click, Extract All - then start this again from the unpacked folder.",
|
|
773
807
|
"ui.pack.launch.failed": "Strom Research could not be installed. Install it by the line in HOW-TO-CONTINUE.txt and run this again.",
|
|
808
|
+
"ui.trees.remove": "take a family tree off this computer",
|
|
809
|
+
"ui.session.finish.done": "Session {session} (task {task}) is asked to finish: its agent sees it at its next strom command, writes down what it found and closes the session.",
|
|
810
|
+
"ui.session.finish.none": "no session is at work in this research",
|
|
811
|
+
"ui.session.finish.which": "several sessions are at work ({sessions}) — say which one",
|
|
812
|
+
"ui.session.finish.closed": "session {session} is closed already",
|
|
813
|
+
"ui.remove.which": "Which family tree to take off this computer?",
|
|
814
|
+
"ui.remove.person": "only the person at the computer takes a family tree off it",
|
|
815
|
+
"ui.remove.busy": "somebody is at work on \"{name}\" right now — it can go once the work is over",
|
|
816
|
+
"ui.remove.what": "\"{name}\" ({root}) · people: {persons}",
|
|
817
|
+
"ui.remove.backup": "Pack it first as a backup, with all its images ({mb} MB)? It comes back any time with strom unpack.",
|
|
818
|
+
"ui.remove.backup.done": " backup: {file}",
|
|
819
|
+
"ui.remove.images": "Into the trash too the images no other family tree uses ({n}, {mb} MB)?",
|
|
820
|
+
"ui.remove.type": "To confirm, type the name of the family tree ({name}) — anything else takes nothing off:",
|
|
821
|
+
"ui.remove.cancelled": "Nothing was taken off.",
|
|
822
|
+
"ui.remove.done": "\"{name}\" is in the trash.",
|
|
823
|
+
"ui.remove.live": " the Strom app no longer follows it live",
|
|
824
|
+
"ui.remove.images.done": " its images in the trash: {n} ({mb} MB)",
|
|
825
|
+
"ui.remove.images.folder": "{name} – images",
|
|
826
|
+
"ui.remove.backup.back": " it comes back from the backup: strom unpack \"{file}\"",
|
|
827
|
+
"ui.remove.trash.alone": " it comes back from the system's trash, until it is emptied",
|
|
828
|
+
"ui.remove.next": "Now you work on \"{name}\" — another one: Another family tree in the menu.",
|
|
829
|
+
"ui.remove.trash": " or from the system's trash, until it is emptied",
|
|
774
830
|
};
|
|
775
831
|
/** A text for the person, in their language. */
|
|
776
832
|
export function ui(lang, key, values = {}) {
|
package/dist/commands/guide.js
CHANGED
|
@@ -59,6 +59,10 @@ conversation opened anywhere, a bot on its own server)
|
|
|
59
59
|
then tell the user what was found (strom person card …) and what comes next (strom plan), and ask
|
|
60
60
|
before the next session. The next step is strom's queue: what the user wants instead becomes a
|
|
61
61
|
task first (strom task add …, or strom research new …) — never search outside a session and its task.
|
|
62
|
+
The user names a research (G…) to work on: its tasks only — strom session start --research G….
|
|
63
|
+
A side of the family no direction reaches (a spouse's line) is a direction of its own: strom research
|
|
64
|
+
new --person <them>. The user wants a direction to wait or to end: strom research pause|done G… (its
|
|
65
|
+
tasks leave the queue, nothing is deleted; strom research resume G… brings them back).
|
|
62
66
|
In a conversation the next task is best begun in a fresh context (Claude Code: /clear) — suggest
|
|
63
67
|
it to the user; many tasks waiting: the agent working on its own (strom run, the user starts it
|
|
64
68
|
from strom's menu), every task in a fresh session
|
|
@@ -153,7 +157,8 @@ THE STROM APP'S EDITS, ANOTHER FAMILY TREE (the user changed the tree in the Str
|
|
|
153
157
|
STORIES OF THE ANCESTORS (the setting stories — on by default; strom shows it)
|
|
154
158
|
Once records tell a person's life (a baptism and more facts from records), strom proposes a narrate
|
|
155
159
|
task: write the story for the family book — plain words, the research language, every statement on a
|
|
156
|
-
recorded fact (strom story set P… --text @notes/story-P….md --fact E…
|
|
160
|
+
recorded fact (strom story set P… --text @notes/story-P….md --title "…" --fact E… …; paragraphs,
|
|
161
|
+
"## " subheadings, "- " bullets, **bold**, *italic* — nothing else shows in the app). More facts later: it
|
|
157
162
|
proposes adding to it. Stories wait behind all the research; working alone
|
|
158
163
|
(strom run), every few sessions one comes first. In a conversation the user leads: write one when they
|
|
159
164
|
want it, or offer it when the research has a pause. A story is a draft until the user approves it (strom story approve P…; the Strom app asks it too). When strom says to tell
|
package/dist/commands/links.js
CHANGED
|
@@ -80,6 +80,10 @@ function who(p) {
|
|
|
80
80
|
function aboutPerson(lang, p) {
|
|
81
81
|
return ui(lang, "ui.link.chat.say", { person: who(p) });
|
|
82
82
|
}
|
|
83
|
+
/** The conversation's first message about one direction: its name and ID, from the research's own record. */
|
|
84
|
+
function aboutDirection(lang, r) {
|
|
85
|
+
return ui(lang, "ui.link.chat.research.say", { name: r.name, id: r.id });
|
|
86
|
+
}
|
|
83
87
|
/** The conversation's first message about a conflict: its ID and title from the research itself. */
|
|
84
88
|
function aboutConflict(root, ctx, id) {
|
|
85
89
|
const tree = Tree.open(root, ctx.env);
|
|
@@ -121,7 +125,8 @@ async function inTerminal(ctx, link) {
|
|
|
121
125
|
// commands, which ask themselves
|
|
122
126
|
case "open":
|
|
123
127
|
case "app":
|
|
124
|
-
|
|
128
|
+
case "live":
|
|
129
|
+
return { text: "", data: { done: (await run(link.action === "open" ? [] : link.action === "live" ? ["app", "--live"] : ["app"])) === 0, action: link.action, tree: root } };
|
|
125
130
|
case "update":
|
|
126
131
|
case "setup":
|
|
127
132
|
return end((await run([link.action])) === 0);
|
|
@@ -158,6 +163,14 @@ async function inTerminal(ctx, link) {
|
|
|
158
163
|
const { undoSending } = await import("../cli/menu-links.js");
|
|
159
164
|
return end(await undoSending(ctx, run, treeLang, root, link.intake));
|
|
160
165
|
}
|
|
166
|
+
case "direction": {
|
|
167
|
+
const { directionDo } = await import("../cli/menu-links.js");
|
|
168
|
+
return end(await directionDo(ctx, run, treeLang, root, link.id, link.do));
|
|
169
|
+
}
|
|
170
|
+
case "finish": {
|
|
171
|
+
const { finishSession } = await import("../cli/menu-links.js");
|
|
172
|
+
return end(await finishSession(ctx, run, treeLang, root, link.session));
|
|
173
|
+
}
|
|
161
174
|
}
|
|
162
175
|
const named = "person" in link ? link.person : undefined;
|
|
163
176
|
const person = named ? personOf(root, ctx, named) : undefined;
|
|
@@ -166,11 +179,20 @@ async function inTerminal(ctx, link) {
|
|
|
166
179
|
const { asks } = await import("../cli/menu-links.js");
|
|
167
180
|
switch (link.action) {
|
|
168
181
|
case "chat": {
|
|
182
|
+
// one direction: its tasks only (the research's own name and ID in the first message)
|
|
183
|
+
const direction = link.research ? tree.get(link.research) : undefined;
|
|
184
|
+
if (link.research && direction?.type !== "research")
|
|
185
|
+
return said("ui.link.direction.none", { id: link.research });
|
|
169
186
|
// the agent costs: said and asked first, then the conversation as strom chat starts it
|
|
170
|
-
const what =
|
|
187
|
+
const what = direction
|
|
188
|
+
? ui(treeLang, "ui.link.what.chat.research", { agent, name: direction.name })
|
|
189
|
+
: person
|
|
190
|
+
? ui(treeLang, "ui.link.what.chat.person", { agent, person: who(person) })
|
|
191
|
+
: ui(treeLang, "ui.link.what.chat", { agent });
|
|
171
192
|
if (!(await asks(ctx, treeLang, root, what, ui(treeLang, "ui.link.cost"))))
|
|
172
193
|
return { text: "", data: { done: false, action: link.action, tree: root } };
|
|
173
|
-
|
|
194
|
+
const first = direction ? aboutDirection(treeLang, direction) : person ? aboutPerson(treeLang, person) : undefined;
|
|
195
|
+
return { text: "", data: { done: (await run(["chat", ...(first ? [`--say=${first}`] : [])])) === 0, action: link.action, tree: root } };
|
|
174
196
|
}
|
|
175
197
|
case "story": {
|
|
176
198
|
const { approveStory } = await import("../cli/menu-links.js");
|
|
@@ -206,6 +228,9 @@ async function withoutTerminal(ctx, link, root) {
|
|
|
206
228
|
const run = await runnerOf(ctx)(root);
|
|
207
229
|
if (link.action === "app")
|
|
208
230
|
return { text: "", data: { done: (await run(["app"], true)) === 0, action: "app", tree: root } };
|
|
231
|
+
// followed live: the bridge started (or the one running), its address into the app's window — strom app --live
|
|
232
|
+
if (link.action === "live")
|
|
233
|
+
return { text: "", data: { done: (await run(["app", "--live"], true)) === 0, action: "live", tree: root } };
|
|
209
234
|
if (link.action !== "chat" && !(link.action === "conflict" && link.do === "agent"))
|
|
210
235
|
return undefined;
|
|
211
236
|
const tree = Tree.open(root, ctx.env);
|
|
@@ -217,6 +242,12 @@ async function withoutTerminal(ctx, link, root) {
|
|
|
217
242
|
if (!first)
|
|
218
243
|
return say(ctx, "ui.link.conflict.none", { id: link.id }, { tree: link.tree });
|
|
219
244
|
}
|
|
245
|
+
else if (link.action === "chat" && link.research) {
|
|
246
|
+
const direction = tree.get(link.research);
|
|
247
|
+
if (direction?.type !== "research")
|
|
248
|
+
return say(ctx, "ui.link.direction.none", { id: link.research }, { tree: link.tree });
|
|
249
|
+
first = aboutDirection(tree.lang, direction);
|
|
250
|
+
}
|
|
220
251
|
else if (link.action === "chat" && link.person) {
|
|
221
252
|
const person = personOf(root, ctx, link.person);
|
|
222
253
|
if (!person)
|
|
@@ -251,6 +282,7 @@ register({
|
|
|
251
282
|
"strom-research://excerpt?tree=<id>&source=S0042&clip=<its mark> — the excerpt from the scan, not made smaller, in the\n" +
|
|
252
283
|
"viewer of the system (the page in the online archive when the scan is not here). Nothing is written.\n" +
|
|
253
284
|
"strom-research://app?tree=<id> — the research as it is now into the app's open window (strom app).\n" +
|
|
285
|
+
"strom-research://live?tree=<id> — the research followed live in the app's window (strom app --live).\n" +
|
|
254
286
|
"strom-research://open?tree=<id> — the research's menu in a terminal.\n" +
|
|
255
287
|
"strom-research://chat?tree=<id>[&person=P0012] — a conversation with the agent (strom chat; of one person: its first\n" +
|
|
256
288
|
"message names only who). strom-research://task?tree=<id>&task=T0007 — the task that waits for the user, answered.\n" +
|
|
@@ -260,6 +292,10 @@ register({
|
|
|
260
292
|
"strom-research://conflict?tree=<id>&id=X0007&do=decide|agent — decided by the user (which claim, why) or left to the agent.\n" +
|
|
261
293
|
"strom-research://story?tree=<id>&person=P0012&do=final — the story approved (strom story approve).\n" +
|
|
262
294
|
"strom-research://sync-undo?tree=<id>&intake=I0042 — a sending from the app taken back (strom sync undo).\n" +
|
|
295
|
+
"strom-research://direction?tree=<id>&id=G0002&do=pause|done|resume — a direction paused, ended (the reason asked) or\n" +
|
|
296
|
+
"taken up again (strom research pause|done|resume).\n" +
|
|
297
|
+
"strom-research://chat?tree=<id>&research=G0002 — a conversation about one direction, its tasks only.\n" +
|
|
298
|
+
"strom-research://finish?tree=<id>&session=N0012 — the session at work asked to finish (strom session finish).\n" +
|
|
263
299
|
"strom-research://update|sessions|setup?tree=<id> — strom update, the agent's sessions and their cost, strom setup.\n" +
|
|
264
300
|
"strom-research://new?app=<the app's mark> — a tree of the app becomes a new research: named, made, the app hands the\n" +
|
|
265
301
|
"tree to its bridge (?adopt=, GET/POST /adopt), taken in as leads, sent back to the app.\n" +
|
package/dist/commands/pack.js
CHANGED
|
@@ -14,6 +14,25 @@ import { setupWizard } from "../cli/wizard.js";
|
|
|
14
14
|
import { isAgent } from "../core/which.js";
|
|
15
15
|
import { ensureShared } from "./setup.js";
|
|
16
16
|
const mb = (bytes) => (bytes / 1e6 < 10 ? (bytes / 1e6).toFixed(1) : Math.round(bytes / 1e6).toString());
|
|
17
|
+
/** Where a package goes unless named: the desktop (else the home), the tree's name and the day, a free name. */
|
|
18
|
+
export function defaultPackFile(ctx, tree) {
|
|
19
|
+
const base = `${safeFolderName(tree.config.name) || "tree"} ${new Date().toISOString().slice(0, 10)}`;
|
|
20
|
+
const desk = desktopDir(ctx.env);
|
|
21
|
+
const dir = fs.existsSync(desk) ? desk : userHome(ctx.env);
|
|
22
|
+
let out = path.join(dir, `${base}.zip`);
|
|
23
|
+
for (let n = 2; fs.existsSync(out); n++)
|
|
24
|
+
out = path.join(dir, `${base} (${n}).zip`);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/** The package with its how-to and launchers in the research language. */
|
|
28
|
+
export function packTree(tree, shared, out, all) {
|
|
29
|
+
const readme = {
|
|
30
|
+
name: ui(tree.config.lang, "ui.pack.readme.file"),
|
|
31
|
+
text: ui(tree.config.lang, "ui.pack.readme", { name: tree.config.name, win: INSTALL_WINDOWS, unix: INSTALL_UNIX }),
|
|
32
|
+
};
|
|
33
|
+
const say = { extract: ui(tree.config.lang, "ui.pack.launch.extract"), failed: ui(tree.config.lang, "ui.pack.launch.failed") };
|
|
34
|
+
return writePack(tree, shared, out, { all, readme, say });
|
|
35
|
+
}
|
|
17
36
|
register({
|
|
18
37
|
path: ["pack"],
|
|
19
38
|
summary: "Pack the research into one ZIP file for someone to go on with it (the tree, the images its records stand on, the connectors)",
|
|
@@ -33,18 +52,7 @@ register({
|
|
|
33
52
|
const shared = ctx.settings.shared().value;
|
|
34
53
|
const lang = ctx.uiLang();
|
|
35
54
|
const all = Boolean(opts["all-images"]);
|
|
36
|
-
|
|
37
|
-
const base = `${safeFolderName(tree.config.name) || "tree"} ${day}`;
|
|
38
|
-
let out;
|
|
39
|
-
if (opts.out)
|
|
40
|
-
out = ctx.resolvePath(String(opts.out));
|
|
41
|
-
else {
|
|
42
|
-
const desk = desktopDir(ctx.env);
|
|
43
|
-
const dir = fs.existsSync(desk) ? desk : userHome(ctx.env);
|
|
44
|
-
out = path.join(dir, `${base}.zip`);
|
|
45
|
-
for (let n = 2; fs.existsSync(out); n++)
|
|
46
|
-
out = path.join(dir, `${base} (${n}).zip`);
|
|
47
|
-
}
|
|
55
|
+
let out = opts.out ? ctx.resolvePath(String(opts.out)) : defaultPackFile(ctx, tree);
|
|
48
56
|
if (!out.toLowerCase().endsWith(".zip"))
|
|
49
57
|
out += ".zip";
|
|
50
58
|
if (fs.existsSync(out))
|
|
@@ -54,12 +62,7 @@ register({
|
|
|
54
62
|
const bytes = plan.media.reduce((s, m) => s + m.size, 0);
|
|
55
63
|
return { text: `dry run: would pack "${tree.config.name}" with ${plan.media.length} image(s) (${mb(bytes)} MB) into ${ctx.display(out)} — nothing written`, data: { out, plan } };
|
|
56
64
|
}
|
|
57
|
-
const
|
|
58
|
-
name: ui(tree.config.lang, "ui.pack.readme.file"),
|
|
59
|
-
text: ui(tree.config.lang, "ui.pack.readme", { name: tree.config.name, win: INSTALL_WINDOWS, unix: INSTALL_UNIX }),
|
|
60
|
-
};
|
|
61
|
-
const say = { extract: ui(tree.config.lang, "ui.pack.launch.extract"), failed: ui(tree.config.lang, "ui.pack.launch.failed") };
|
|
62
|
-
const r = writePack(tree, shared, out, { all, readme, say });
|
|
65
|
+
const r = packTree(tree, shared, out, all);
|
|
63
66
|
const took = r.plan.media.reduce((s, m) => s + m.size, 0);
|
|
64
67
|
const text = lines(ui(lang, "ui.pack.done", { name: tree.config.name, file: ctx.display(r.file), mb: mb(r.bytes) }), ui(lang, "ui.pack.saved"), ui(lang, all ? "ui.pack.images.all" : "ui.pack.images", { n: r.plan.media.length, mb: mb(took) }), r.plan.left.count ? ui(lang, "ui.pack.left", { n: r.plan.left.count, mb: mb(r.plan.left.bytes) }) : undefined, r.plan.connectors.length ? ui(lang, "ui.pack.connectors", { list: r.plan.connectors.join(", ") }) : undefined, r.plan.left.count && r.plan.left.connectors ? ui(lang, "ui.pack.noconnector") : undefined, r.plan.missing.length ? ui(lang, "ui.pack.missing", { n: r.plan.missing.length }) : undefined, "", ui(lang, "ui.pack.send"));
|
|
65
68
|
return { text, data: { file: r.file, bytes: r.bytes, manifest: r.manifest, missing: r.plan.missing } };
|
|
@@ -12,6 +12,8 @@ import { UsageError } from "../core/errors.js";
|
|
|
12
12
|
import { REVIEW_SCOPES } from "../core/model.js";
|
|
13
13
|
import { ancestorGenerations, displayName, label, lifespan, parentsOf, resolvePerson } from "../core/people.js";
|
|
14
14
|
import { foldText } from "../core/text.js";
|
|
15
|
+
import { now } from "../core/tree.js";
|
|
16
|
+
import { directionOf, scopes } from "../core/directions.js";
|
|
15
17
|
import { update } from "../core/records.js";
|
|
16
18
|
import { isAgent } from "../core/which.js";
|
|
17
19
|
function int(v, name) {
|
|
@@ -109,7 +111,46 @@ register({
|
|
|
109
111
|
return { text: 'no researches yet → strom research new "<name>" --new-person "<Given /Surname/>"', data: { researches: [] } };
|
|
110
112
|
return { text: table(all.map((r) => researchLine(tree, r))), data: { researches: all } };
|
|
111
113
|
},
|
|
112
|
-
}, {
|
|
114
|
+
}, ...["pause", "resume", "done"].map((act) => ({
|
|
115
|
+
path: ["research", act],
|
|
116
|
+
summary: act === "pause"
|
|
117
|
+
? "Pause a research direction: its tasks wait out of the queue, nothing new is proposed for it"
|
|
118
|
+
: act === "resume"
|
|
119
|
+
? "Resume a paused or finished research direction: its tasks back in the queue, what it would propose added"
|
|
120
|
+
: "End a research direction (done): its tasks leave the queue, nothing is deleted",
|
|
121
|
+
group: "research",
|
|
122
|
+
tree: true,
|
|
123
|
+
writes: true,
|
|
124
|
+
description: "The user's decision about where the research goes (an agent runs it when the user asks). Nothing is\n" +
|
|
125
|
+
"deleted: the direction's tasks stay as they are, out of the queue while it is paused or done; a session\n" +
|
|
126
|
+
"at work on one of them goes on. strom research resume brings them back.",
|
|
127
|
+
args: [{ name: "research", description: "ID (G0001) or part of the name", required: true }],
|
|
128
|
+
options: act === "resume" ? [] : [{ name: "reason", type: "string", value: "<text>", description: "why (kept as a note of the research)" }],
|
|
129
|
+
examples: act === "pause" ? ['strom research pause G0001 --reason "the family asked to wait"'] : act === "resume" ? ["strom research resume G0001"] : ["strom research done G0001"],
|
|
130
|
+
run(ctx, { args, opts }) {
|
|
131
|
+
const tree = ctx.tree();
|
|
132
|
+
const r = resolveResearch(tree, args[0]);
|
|
133
|
+
const state = act === "pause" ? "paused" : act === "resume" ? "active" : "done";
|
|
134
|
+
const reason = typeof opts.reason === "string" && opts.reason.trim() ? opts.reason.trim() : undefined;
|
|
135
|
+
const waiting = (id) => {
|
|
136
|
+
const all = scopes(tree);
|
|
137
|
+
return tree.list("task").filter((t) => ["open", "parked", "waiting"].includes(t.state) && directionOf(tree, t, all) === id).length;
|
|
138
|
+
};
|
|
139
|
+
if (r.state === state)
|
|
140
|
+
return { text: `${r.id} is ${state} already`, data: { research: r, changed: false, tasks: waiting(r.id) } };
|
|
141
|
+
const next = update(tree, r.id, "research", (x) => ({ ...x, state, stateSince: now(), notes: reason ? [...x.notes, { text: `${state}: ${reason}`, at: new Date().toISOString(), by: tree.actor }] : x.notes }), {
|
|
142
|
+
op: `research.${act}`,
|
|
143
|
+
summary: `${r.id} ${state}: ${r.name}`,
|
|
144
|
+
...(reason ? { reason } : {}),
|
|
145
|
+
});
|
|
146
|
+
const created = act === "resume" ? applyFrontier(tree, next) : [];
|
|
147
|
+
const n = waiting(r.id);
|
|
148
|
+
return {
|
|
149
|
+
text: lines(...tree.written.map((o) => o.summary), tree.dryRun ? "(dry run — nothing written)" : undefined, act === "resume" ? `its tasks are back in the queue: ${n}` : `its tasks wait out of the queue: ${n} → strom research resume ${r.id}`),
|
|
150
|
+
data: { research: next, changed: true, tasks: n, created },
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
})), {
|
|
113
154
|
path: ["research", "show"],
|
|
114
155
|
summary: "One research: goal, focus person, known ancestors, what is missing",
|
|
115
156
|
group: "research",
|