stratum-cli 0.8.0-beta.1 → 0.8.0-beta.2

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.md CHANGED
@@ -6,6 +6,15 @@ El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y l
6
6
 
7
7
  > Las versiones anteriores a la 0.2.1-beta.0 solo existen como tags de git: la primera publicación en npm fue la 0.2.1-beta.0.
8
8
 
9
+ ## [0.8.0-beta.2] — 2026-10-12
10
+
11
+ Tercera pre-release (`npm install -g stratum-cli@beta`), con más arreglos de lo visto al probar la beta.
12
+
13
+ ### Corregido
14
+ - **El terminal parpadeaba con varios subagentes trabajando.** El árbol de subagentes listaba todas sus tool calls; al superar el alto del terminal, la pantalla entera se borraba y repintaba en cada actualización. Ahora cada subagente enseña solo las últimas (entre 1 y 6, según el alto del terminal) y resume las anteriores en una línea; el detalle completo sigue en `/subagents`.
15
+ - La etiqueta de una tool call enseña el argumento que la identifica (la ruta, el comando, el patrón) aunque el modelo lo envíe detrás de otros: antes salía `read_file: 700` cuando `limit` llegaba antes que `path`.
16
+ - Dos sesiones nuevas guardadas en el mismo segundo podían recibir el mismo id y la segunda pisaba a la primera. Ahora el id generado se comprueba contra el disco.
17
+
9
18
  ## [0.8.0-beta.1] — 2026-10-12
10
19
 
11
20
  Segunda pre-release (`npm install -g stratum-cli@beta`), con lo visto al probar la beta.0.
@@ -284,6 +293,7 @@ Primera versión etiquetada. Hitos 0 y 1.
284
293
  - `/init` para generar o actualizar `STRATUM.md`.
285
294
  - Render de markdown de las respuestas.
286
295
 
296
+ [0.8.0-beta.2]: https://github.com/choruzo/CLI/compare/v0.8.0-beta.1...v0.8.0-beta.2
287
297
  [0.8.0-beta.1]: https://github.com/choruzo/CLI/compare/v0.8.0-beta.0...v0.8.0-beta.1
288
298
  [0.8.0-beta.0]: https://github.com/choruzo/CLI/compare/v0.7.0...v0.8.0-beta.0
289
299
  [0.7.0]: https://github.com/choruzo/CLI/compare/v0.6.0...v0.7.0
package/dist/index.js CHANGED
@@ -19726,6 +19726,16 @@ var init_store2 = __esm({
19726
19726
  mkdirSync7(this.sessionsDir, { recursive: true });
19727
19727
  }
19728
19728
  }
19729
+ /**
19730
+ * Un id que no está en disco. El id lleva la hora al segundo y tres
19731
+ * caracteres al azar: dos sesiones guardadas en el mismo segundo pueden
19732
+ * coincidir, y la segunda pisaría a la primera.
19733
+ */
19734
+ freshSessionId() {
19735
+ let id = generateSessionId();
19736
+ while (existsSync16(this.sessionPath(id))) id = generateSessionId();
19737
+ return id;
19738
+ }
19729
19739
  sessionPath(id) {
19730
19740
  if (!isSessionId(id)) {
19731
19741
  throw new Error(`Id de sesi\xF3n inv\xE1lido: ${JSON.stringify(id.slice(0, 80))}`);
@@ -19747,14 +19757,14 @@ var init_store2 = __esm({
19747
19757
  async save(params) {
19748
19758
  this.ensureDir();
19749
19759
  const now = (/* @__PURE__ */ new Date()).toISOString();
19750
- let id = params.existingId ?? generateSessionId();
19760
+ let id = params.existingId ?? this.freshSessionId();
19751
19761
  let createdAt = params.createdAt ?? now;
19752
19762
  let forkedFrom = params.forkedFrom;
19753
19763
  if (params.existingId) {
19754
19764
  const onDisk = this.diskUpdatedAt(params.existingId);
19755
19765
  if (onDisk !== null && onDisk !== params.expectedUpdatedAt) {
19756
19766
  forkedFrom = params.existingId;
19757
- id = generateSessionId();
19767
+ id = this.freshSessionId();
19758
19768
  createdAt = now;
19759
19769
  log21.warn("session changed on disk: saving as a fork", { from: forkedFrom, to: id });
19760
19770
  }
@@ -20781,6 +20791,51 @@ var init_spinner = __esm({
20781
20791
  }
20782
20792
  });
20783
20793
 
20794
+ // src/cli/ui/tool-arg-label.ts
20795
+ function labelOf(value2) {
20796
+ if (value2 === null || value2 === void 0) return "";
20797
+ if (typeof value2 !== "object") return String(value2);
20798
+ if (!Array.isArray(value2)) return JSON.stringify(value2);
20799
+ const first = value2[0];
20800
+ const head = typeof first === "object" && first !== null ? Object.values(first).find((v) => typeof v === "string") ?? JSON.stringify(first) : String(first ?? "");
20801
+ return value2.length > 1 ? `${head} (+${value2.length - 1})` : head;
20802
+ }
20803
+ function primaryArg(input) {
20804
+ if (!input) return "";
20805
+ const keys = Object.keys(input);
20806
+ if (keys.length === 0) return "";
20807
+ const key = IDENTIFYING_KEYS.find((k) => typeof input[k] === "string" && input[k] !== "") ?? keys[0];
20808
+ return labelOf(input[key]);
20809
+ }
20810
+ function tailWindow(items, max) {
20811
+ const keep = Math.max(1, Math.floor(max));
20812
+ if (items.length <= keep) return { hidden: 0, visible: [...items] };
20813
+ return { hidden: items.length - keep, visible: items.slice(-keep) };
20814
+ }
20815
+ function treeToolBudget(rows, nodes, reservedRows = 22) {
20816
+ const perNode = Math.floor((rows - reservedRows) / Math.max(1, nodes)) - 2;
20817
+ return Math.min(6, Math.max(1, perNode));
20818
+ }
20819
+ var IDENTIFYING_KEYS;
20820
+ var init_tool_arg_label = __esm({
20821
+ "src/cli/ui/tool-arg-label.ts"() {
20822
+ "use strict";
20823
+ init_esm_shims();
20824
+ IDENTIFYING_KEYS = [
20825
+ "command",
20826
+ "path",
20827
+ "file_path",
20828
+ "filePath",
20829
+ "pattern",
20830
+ "query",
20831
+ "url",
20832
+ "task",
20833
+ "jobId",
20834
+ "name"
20835
+ ];
20836
+ }
20837
+ });
20838
+
20784
20839
  // src/cli/ui/ToolCallBlock.tsx
20785
20840
  import { Box, Text } from "ink";
20786
20841
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -20811,15 +20866,7 @@ function formatInput(state) {
20811
20866
  return truncateLabel(`${from} \u2192 ${to}`);
20812
20867
  }
20813
20868
  }
20814
- return truncateLabel(labelOf(src[keys[0]]));
20815
- }
20816
- function labelOf(value2) {
20817
- if (value2 === null || value2 === void 0) return "";
20818
- if (typeof value2 !== "object") return String(value2);
20819
- if (!Array.isArray(value2)) return JSON.stringify(value2);
20820
- const first = value2[0];
20821
- const head = typeof first === "object" && first !== null ? Object.values(first).find((v) => typeof v === "string") ?? JSON.stringify(first) : String(first ?? "");
20822
- return value2.length > 1 ? `${head} (+${value2.length - 1})` : head;
20869
+ return truncateLabel(primaryArg(src));
20823
20870
  }
20824
20871
  function formatDuration(ms) {
20825
20872
  return `${(ms / 1e3).toFixed(1)}s`;
@@ -20940,6 +20987,7 @@ var init_ToolCallBlock = __esm({
20940
20987
  init_esm_shims();
20941
20988
  init_theme();
20942
20989
  init_spinner();
20990
+ init_tool_arg_label();
20943
20991
  MAX_EXPANDED_LINES = 10;
20944
20992
  SLOW_MS = 1e3;
20945
20993
  }
@@ -22398,7 +22446,7 @@ var init_SubagentBlock = __esm({
22398
22446
  });
22399
22447
 
22400
22448
  // src/cli/ui/AgentTree.tsx
22401
- import { Box as Box12, Text as Text13 } from "ink";
22449
+ import { Box as Box12, Text as Text13, useStdout as useStdout3 } from "ink";
22402
22450
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
22403
22451
  function fmtDur2(ms) {
22404
22452
  return `${(ms / 1e3).toFixed(1)}s`;
@@ -22408,10 +22456,7 @@ function truncate2(text, max) {
22408
22456
  return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
22409
22457
  }
22410
22458
  function firstArg(tc) {
22411
- const src = tc.input ?? {};
22412
- const keys = Object.keys(src);
22413
- if (keys.length === 0) return "";
22414
- return truncate2(String(src[keys[0]] ?? ""), 40);
22459
+ return truncate2(primaryArg(tc.input), 40);
22415
22460
  }
22416
22461
  function ToolLine({ tc }) {
22417
22462
  const icon = tc.status === "completed" ? "\u2713" : tc.status === "error" ? "\u2717" : "\u27F3";
@@ -22446,7 +22491,7 @@ function nodeIcon(status) {
22446
22491
  return { icon: "\u22B3", color: theme.accent };
22447
22492
  }
22448
22493
  }
22449
- function SubagentNode({ node, speaking, focused, expanded, isLast, now }) {
22494
+ function SubagentNode({ node, speaking, focused, expanded, isLast, now, maxToolLines }) {
22450
22495
  const running = node.status === "running";
22451
22496
  const elapsedMs = node.startedAt ? Math.max(0, now - node.startedAt) : 0;
22452
22497
  const { icon, color } = nodeIcon(node.status);
@@ -22458,7 +22503,7 @@ function SubagentNode({ node, speaking, focused, expanded, isLast, now }) {
22458
22503
  node.iterations !== void 0 ? `${node.iterations} it` : ""
22459
22504
  ].filter(Boolean).join(" \xB7 ");
22460
22505
  const tail2 = node.status === "failed" && node.error ? truncate2(node.error, 44) : node.status === "completed" && node.summary ? truncate2(node.summary, 44) : truncate2(node.task, 44);
22461
- const tcs = node.toolCalls ?? [];
22506
+ const { hidden, visible: tcs } = tailWindow(node.toolCalls ?? [], maxToolLines);
22462
22507
  const files = node.filesChanged ?? [];
22463
22508
  return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
22464
22509
  /* @__PURE__ */ jsxs11(Text13, { wrap: "truncate-end", children: [
@@ -22482,6 +22527,15 @@ function SubagentNode({ node, speaking, focused, expanded, isLast, now }) {
22482
22527
  ] }),
22483
22528
  focused ? /* @__PURE__ */ jsx13(Text13, { color: theme.accent, children: " \u25C0" }) : null
22484
22529
  ] }),
22530
+ hidden > 0 && /* @__PURE__ */ jsxs11(Text13, { color: theme.textDisabled, wrap: "truncate-end", children: [
22531
+ " ",
22532
+ "\u2026 ",
22533
+ hidden,
22534
+ " ",
22535
+ hidden === 1 ? "acci\xF3n anterior" : "acciones anteriores",
22536
+ " ",
22537
+ "(/subagents para verlas)"
22538
+ ] }),
22485
22539
  tcs.map((tc) => /* @__PURE__ */ jsx13(ToolLine, { tc }, tc.id)),
22486
22540
  expanded && node.summary && /* @__PURE__ */ jsxs11(Text13, { color: theme.textFaint, wrap: "truncate-end", children: [
22487
22541
  " ",
@@ -22507,6 +22561,8 @@ function AgentTree({
22507
22561
  expandedBlockIds,
22508
22562
  now = Date.now()
22509
22563
  }) {
22564
+ const { stdout } = useStdout3();
22565
+ const maxToolLines = treeToolBudget(stdout?.rows ?? 24, nodes.length);
22510
22566
  const allTerminal = nodes.every((n) => TERMINAL.has(n.status));
22511
22567
  if (allTerminal) {
22512
22568
  const completed = nodes.filter((n) => n.status === "completed").length;
@@ -22552,7 +22608,8 @@ function AgentTree({
22552
22608
  focused: focusedBlockId === n.id,
22553
22609
  expanded: expandedBlockIds?.has(n.id) ?? false,
22554
22610
  isLast: i === nodes.length - 1,
22555
- now
22611
+ now,
22612
+ maxToolLines
22556
22613
  },
22557
22614
  n.id
22558
22615
  ))
@@ -22565,6 +22622,7 @@ var init_AgentTree = __esm({
22565
22622
  init_esm_shims();
22566
22623
  init_theme();
22567
22624
  init_spinner();
22625
+ init_tool_arg_label();
22568
22626
  TERMINAL = /* @__PURE__ */ new Set([
22569
22627
  "completed",
22570
22628
  "failed",
@@ -22786,7 +22844,7 @@ var init_AgentMessage = __esm({
22786
22844
  });
22787
22845
 
22788
22846
  // src/cli/ui/MessageList.tsx
22789
- import { Box as Box15, Static as Static2, useStdout as useStdout3 } from "ink";
22847
+ import { Box as Box15, Static as Static2, useStdout as useStdout4 } from "ink";
22790
22848
  import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
22791
22849
  function renderItem(item, key, focusedBlockId, expandedBlockIds, debug, now, liveTextLines, liveColumns, liveActionLimit) {
22792
22850
  if (item.kind === "user") {
@@ -22836,7 +22894,7 @@ function MessageList({
22836
22894
  now = Date.now(),
22837
22895
  availableRows
22838
22896
  }) {
22839
- const { stdout } = useStdout3();
22897
+ const { stdout } = useStdout4();
22840
22898
  const { contentWidth } = resolveLayout(stdout.columns ?? 80, stdout.rows ?? 24);
22841
22899
  const terminalRows = availableRows ?? stdout.rows ?? 24;
22842
22900
  const liveActionLimit = Math.max(1, Math.min(6, Math.floor((terminalRows - 8) / 2)));
@@ -23563,7 +23621,7 @@ var init_FatalError = __esm({
23563
23621
  });
23564
23622
 
23565
23623
  // src/cli/ui/ConversationView.tsx
23566
- import { Box as Box26, Text as Text27, useStdout as useStdout4 } from "ink";
23624
+ import { Box as Box26, Text as Text27, useStdout as useStdout5 } from "ink";
23567
23625
  import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
23568
23626
  function ConversationView({
23569
23627
  completedItems,
@@ -23613,7 +23671,7 @@ function ConversationView({
23613
23671
  const liveNow = useLiveClock(thinking);
23614
23672
  const gateOpen = !!pendingConfirm || !!pendingQuestions?.length || !!pendingApproval;
23615
23673
  const steerable = thinking && !gateOpen && !fatalError;
23616
- const { stdout } = useStdout4();
23674
+ const { stdout } = useStdout5();
23617
23675
  const terminalRows = stdout.rows ?? 24;
23618
23676
  const panelMaxSteps = Math.max(2, Math.min(5, Math.floor(terminalRows / 5)));
23619
23677
  const jobsClock = jobsNow ?? Date.now();
@@ -26961,7 +27019,7 @@ async function settle(step, fn) {
26961
27019
  }
26962
27020
  }
26963
27021
  function resolveVersion() {
26964
- if (true) return "0.8.0-beta.1";
27022
+ if (true) return "0.8.0-beta.2";
26965
27023
  try {
26966
27024
  const thisDir = dirname14(fileURLToPath2(import.meta.url));
26967
27025
  const pkg = JSON.parse(
@@ -32816,7 +32874,7 @@ function resolveResult(ref, dir) {
32816
32874
  }
32817
32875
 
32818
32876
  // src/cli/commands/eval.ts
32819
- var VERSION = true ? "0.8.0-beta.1" : "dev";
32877
+ var VERSION = true ? "0.8.0-beta.2" : "dev";
32820
32878
  var fail2 = (message) => {
32821
32879
  process.stderr.write(`${message}
32822
32880
  `);
@@ -33429,7 +33487,7 @@ var statsCommand = new Command14("stats").description(
33429
33487
  init_loader();
33430
33488
  init_schema_version();
33431
33489
  function resolveVersion2() {
33432
- if (true) return "0.8.0-beta.1";
33490
+ if (true) return "0.8.0-beta.2";
33433
33491
  const thisDir = dirname19(fileURLToPath5(import.meta.url));
33434
33492
  const pkg = JSON.parse(readFileSync26(join32(thisDir, "..", "..", "package.json"), "utf-8"));
33435
33493
  return pkg.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stratum-cli",
3
- "version": "0.8.0-beta.1",
3
+ "version": "0.8.0-beta.2",
4
4
  "description": "Agente CLI extensible sobre un loop ReAct — provider-agnostic (Ollama, llama.cpp, vLLM, LiteLLM, OpenAI), con plan-and-execute, subagentes, MCP y SSH nativo",
5
5
  "keywords": [
6
6
  "cli",