@dimi-agent/cli 0.6.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/main.mjs +148 -85
  2. package/package.json +9 -9
package/dist/main.mjs CHANGED
@@ -378194,6 +378194,21 @@ var init_tool_call = __esmMin((() => {
378194
378194
  get toolCallView() {
378195
378195
  return this.toolCall;
378196
378196
  }
378197
+ /**
378198
+ * Whether this call has reached a terminal state. Ordinary tools finish when
378199
+ * their result lands; agent and read cards derive their phase from events
378200
+ * (subagent lifecycle / read status), falling back to the result. Used by
378201
+ * transcript folding to avoid collapsing calls the user still needs to
378202
+ * watch (a running agent would otherwise disappear into the summary line).
378203
+ */
378204
+ isFinished() {
378205
+ if (this.toolCall.name === "Agent" || this.toolCall.name === "AgentSwarm") {
378206
+ const phase = this.getSubagentSnapshot().phase;
378207
+ return phase === "done" || phase === "failed" || phase === "backgrounded";
378208
+ }
378209
+ if (this.toolCall.name === "Read") return this.getReadSnapshot().phase !== "pending";
378210
+ return this.result !== void 0;
378211
+ }
378197
378212
  /** Notifies the listener when internal state changes, if a group is attached. */
378198
378213
  notifySnapshotChange() {
378199
378214
  this.onSnapshotChange?.();
@@ -378352,6 +378367,7 @@ var init_tool_call = __esmMin((() => {
378352
378367
  const wait = this.waitState;
378353
378368
  if (wait === void 0 || wait.ended || wait.timedOut) return;
378354
378369
  wait.ended = true;
378370
+ wait.endedElapsedSeconds = Math.min(wait.maxSeconds, Math.max(0, Math.floor((Date.now() - wait.startedAtMs) / 1e3)));
378355
378371
  this.stopWaitTimer();
378356
378372
  this.headerText.setText(this.buildHeader());
378357
378373
  this.notifySnapshotChange();
@@ -378621,7 +378637,7 @@ var init_tool_call = __esmMin((() => {
378621
378637
  timerText = `${max} (timeout)`;
378622
378638
  } else if (wait.ended) {
378623
378639
  verb = currentTheme.boldFg("success", "Waited");
378624
- timerText = `${formatElapsed(elapsedSeconds)} / ${max}`;
378640
+ timerText = `${formatElapsed(wait.endedElapsedSeconds ?? elapsedSeconds)} / ${max}`;
378625
378641
  } else {
378626
378642
  verb = currentTheme.boldFg("primary", "Waiting");
378627
378643
  timerText = `${formatElapsed(elapsedSeconds)} / ${max}`;
@@ -387215,6 +387231,82 @@ var init_terminal_notification = __esmMin((() => {
387215
387231
  init_terminal();
387216
387232
  }));
387217
387233
  //#endregion
387234
+ //#region src/tui/utils/transcript-window.ts
387235
+ /**
387236
+ * Read a non-negative integer env var, falling back to `fallback` when it is
387237
+ * unset, empty, negative, or not an integer. `0` is a valid value (call sites
387238
+ * treat it as "feature disabled").
387239
+ */
387240
+ function readEnvInt(name, fallback) {
387241
+ const raw = process.env[name];
387242
+ if (raw === void 0 || raw.trim() === "") return fallback;
387243
+ const value = Number(raw);
387244
+ if (!Number.isInteger(value) || value < 0) return fallback;
387245
+ return value;
387246
+ }
387247
+ /**
387248
+ * Group consecutive entries into turns by `turnId`. Entries with the same
387249
+ * non-undefined `turnId` that are adjacent belong to the same turn.
387250
+ *
387251
+ * Entries with an undefined `turnId` are buffered and attached to the *next*
387252
+ * defined turn. This matters because a user message is appended (with
387253
+ * `turnId: undefined`) before its turn actually starts, so without this
387254
+ * buffering every user message would become its own single-entry turn at the
387255
+ * front and get trimmed first. Any undefined entries left at the tail (no
387256
+ * following turn) become their own turn.
387257
+ */
387258
+ function groupTurns(entries) {
387259
+ const turns = [];
387260
+ let current;
387261
+ let pendingUndefined = [];
387262
+ for (const entry of entries) {
387263
+ const turnId = entry.turnId;
387264
+ if (turnId === void 0) {
387265
+ pendingUndefined.push(entry);
387266
+ continue;
387267
+ }
387268
+ if (current !== void 0 && current.turnId === turnId) current.entries.push(entry);
387269
+ else {
387270
+ current = {
387271
+ turnId,
387272
+ entries: [...pendingUndefined, entry]
387273
+ };
387274
+ pendingUndefined = [];
387275
+ turns.push(current);
387276
+ }
387277
+ }
387278
+ if (pendingUndefined.length > 0) turns.push({
387279
+ turnId: void 0,
387280
+ entries: pendingUndefined
387281
+ });
387282
+ return turns;
387283
+ }
387284
+ /**
387285
+ * Decide which entries to destroy so the remaining turns fit within
387286
+ * `maxTurns`. Returns an empty set when the turn count is within
387287
+ * `maxTurns + hysteresis`. Oldest turns are removed first; the most recent
387288
+ * turn is never removed (it is the active / just-finished turn).
387289
+ */
387290
+ function turnsToTrim(turns, maxTurns, hysteresis) {
387291
+ const toRemove = /* @__PURE__ */ new Set();
387292
+ if (turns.length <= maxTurns + hysteresis) return toRemove;
387293
+ let remaining = turns.length;
387294
+ for (let i = 0; i < turns.length - 1 && remaining > maxTurns; i++) {
387295
+ const turn = turns[i];
387296
+ for (const entry of turn.entries) toRemove.add(entry);
387297
+ remaining--;
387298
+ }
387299
+ return toRemove;
387300
+ }
387301
+ var TRANSCRIPT_MAX_TURNS, TRANSCRIPT_EXPAND_TURNS, TRANSCRIPT_HYSTERESIS, TRANSCRIPT_KEEP_RECENT_STEPS, TRANSCRIPT_KEEP_TRAILING_TOOL_CALLS;
387302
+ var init_transcript_window = __esmMin((() => {
387303
+ TRANSCRIPT_MAX_TURNS = readEnvInt("DIMI_CODE_TUI_MAX_TURNS", 15);
387304
+ TRANSCRIPT_EXPAND_TURNS = readEnvInt("DIMI_CODE_TUI_EXPAND_TURNS", 3);
387305
+ TRANSCRIPT_HYSTERESIS = readEnvInt("DIMI_CODE_TUI_HYSTERESIS", 5);
387306
+ TRANSCRIPT_KEEP_RECENT_STEPS = readEnvInt("DIMI_CODE_TUI_KEEP_RECENT_STEPS", 30);
387307
+ TRANSCRIPT_KEEP_TRAILING_TOOL_CALLS = readEnvInt("DIMI_CODE_TUI_KEEP_TRAILING_TOOL_CALLS", 2);
387308
+ }));
387309
+ //#endregion
387218
387310
  //#region src/tui/controllers/streaming-ui.ts
387219
387311
  var StreamingUIController;
387220
387312
  var init_streaming_ui = __esmMin((() => {
@@ -387230,6 +387322,7 @@ var init_streaming_ui = __esmMin((() => {
387230
387322
  init_event_payload();
387231
387323
  init_terminal_notification();
387232
387324
  init_transcript_id();
387325
+ init_transcript_window();
387233
387326
  StreamingUIController = class {
387234
387327
  host;
387235
387328
  flushTimer;
@@ -387713,6 +387806,7 @@ var init_streaming_ui = __esmMin((() => {
387713
387806
  state.transcriptContainer.addChild(tc);
387714
387807
  state.ui.requestRender();
387715
387808
  }
387809
+ this.host.foldTrailingToolCalls(TRANSCRIPT_KEEP_TRAILING_TOOL_CALLS);
387716
387810
  if (toolCall.name === "ExitPlanMode" && typeof toolCall.args["plan"] !== "string") {
387717
387811
  const session = this.host.requireSession();
387718
387812
  (async () => {
@@ -390461,81 +390555,6 @@ var init_tmux_keyboard = __esmMin((() => {
390461
390555
  TMUX_EXTENDED_KEYS_FORMAT_XTERM_WARNING = "tmux extended-keys-format is xterm. Dimi works best with csi-u. Add `set -g extended-keys-format csi-u` to ~/.tmux.conf and restart tmux.";
390462
390556
  }));
390463
390557
  //#endregion
390464
- //#region src/tui/utils/transcript-window.ts
390465
- /**
390466
- * Read a non-negative integer env var, falling back to `fallback` when it is
390467
- * unset, empty, negative, or not an integer. `0` is a valid value (call sites
390468
- * treat it as "feature disabled").
390469
- */
390470
- function readEnvInt(name, fallback) {
390471
- const raw = process.env[name];
390472
- if (raw === void 0 || raw.trim() === "") return fallback;
390473
- const value = Number(raw);
390474
- if (!Number.isInteger(value) || value < 0) return fallback;
390475
- return value;
390476
- }
390477
- /**
390478
- * Group consecutive entries into turns by `turnId`. Entries with the same
390479
- * non-undefined `turnId` that are adjacent belong to the same turn.
390480
- *
390481
- * Entries with an undefined `turnId` are buffered and attached to the *next*
390482
- * defined turn. This matters because a user message is appended (with
390483
- * `turnId: undefined`) before its turn actually starts, so without this
390484
- * buffering every user message would become its own single-entry turn at the
390485
- * front and get trimmed first. Any undefined entries left at the tail (no
390486
- * following turn) become their own turn.
390487
- */
390488
- function groupTurns(entries) {
390489
- const turns = [];
390490
- let current;
390491
- let pendingUndefined = [];
390492
- for (const entry of entries) {
390493
- const turnId = entry.turnId;
390494
- if (turnId === void 0) {
390495
- pendingUndefined.push(entry);
390496
- continue;
390497
- }
390498
- if (current !== void 0 && current.turnId === turnId) current.entries.push(entry);
390499
- else {
390500
- current = {
390501
- turnId,
390502
- entries: [...pendingUndefined, entry]
390503
- };
390504
- pendingUndefined = [];
390505
- turns.push(current);
390506
- }
390507
- }
390508
- if (pendingUndefined.length > 0) turns.push({
390509
- turnId: void 0,
390510
- entries: pendingUndefined
390511
- });
390512
- return turns;
390513
- }
390514
- /**
390515
- * Decide which entries to destroy so the remaining turns fit within
390516
- * `maxTurns`. Returns an empty set when the turn count is within
390517
- * `maxTurns + hysteresis`. Oldest turns are removed first; the most recent
390518
- * turn is never removed (it is the active / just-finished turn).
390519
- */
390520
- function turnsToTrim(turns, maxTurns, hysteresis) {
390521
- const toRemove = /* @__PURE__ */ new Set();
390522
- if (turns.length <= maxTurns + hysteresis) return toRemove;
390523
- let remaining = turns.length;
390524
- for (let i = 0; i < turns.length - 1 && remaining > maxTurns; i++) {
390525
- const turn = turns[i];
390526
- for (const entry of turn.entries) toRemove.add(entry);
390527
- remaining--;
390528
- }
390529
- return toRemove;
390530
- }
390531
- var TRANSCRIPT_MAX_TURNS, TRANSCRIPT_EXPAND_TURNS, TRANSCRIPT_HYSTERESIS, TRANSCRIPT_KEEP_RECENT_STEPS;
390532
- var init_transcript_window = __esmMin((() => {
390533
- TRANSCRIPT_MAX_TURNS = readEnvInt("DIMI_CODE_TUI_MAX_TURNS", 15);
390534
- TRANSCRIPT_EXPAND_TURNS = readEnvInt("DIMI_CODE_TUI_EXPAND_TURNS", 3);
390535
- TRANSCRIPT_HYSTERESIS = readEnvInt("DIMI_CODE_TUI_HYSTERESIS", 5);
390536
- TRANSCRIPT_KEEP_RECENT_STEPS = readEnvInt("DIMI_CODE_TUI_KEEP_RECENT_STEPS", 30);
390537
- }));
390538
- //#endregion
390539
390558
  //#region src/tui/dimi-tui.ts
390540
390559
  function loadingTipKind(mode) {
390541
390560
  if (mode === "waiting" || mode === "tool") return "moon";
@@ -391894,6 +391913,7 @@ var init_dimi_tui = __esmMin((() => {
391894
391913
  markTranscriptComponent(component, entry);
391895
391914
  this.state.transcriptContainer.addChild(component);
391896
391915
  }
391916
+ if (entry.kind === "tool_call") this.foldTrailingToolCalls(TRANSCRIPT_KEEP_TRAILING_TOOL_CALLS);
391897
391917
  const trimmed = this.trimTranscriptWindow();
391898
391918
  const merged = this.mergeCurrentTurnSteps();
391899
391919
  if (component || trimmed || merged) this.state.ui.requestRender();
@@ -391998,6 +392018,21 @@ var init_dimi_tui = __esmMin((() => {
391998
392018
  return this.foldCurrentTurnSteps(TRANSCRIPT_KEEP_RECENT_STEPS);
391999
392019
  }
392000
392020
  collapseTrailingToolCalls() {
392021
+ this.foldTrailingToolCalls(0);
392022
+ }
392023
+ /**
392024
+ * Fold the trailing contiguous run of tool calls, keeping the most recent
392025
+ * `keepExpanded` calls expanded. `keepExpanded = 0` collapses the whole run
392026
+ * (used when visible content follows, e.g. an assistant message or turn
392027
+ * end); a positive cap folds progressively while the run is still growing
392028
+ * so older calls merge into the summary immediately instead of staying
392029
+ * expanded until the run ends.
392030
+ *
392031
+ * Only finished calls fold — a call that is still running (streaming args,
392032
+ * a live subagent, a pending read) stays visible because its progress is
392033
+ * live information the user is watching.
392034
+ */
392035
+ foldTrailingToolCalls(keepExpanded) {
392001
392036
  const children = this.state.transcriptContainer.children;
392002
392037
  let start = children.length;
392003
392038
  const toolCalls = [];
@@ -392019,21 +392054,49 @@ var init_dimi_tui = __esmMin((() => {
392019
392054
  const standaloneNames = new Set(["AllDone", "WaitFor"]);
392020
392055
  const standaloneCalls = new Set(toolCalls.filter((call) => standaloneNames.has(call.toolCallView.name)));
392021
392056
  const sequenceCalls = toolCalls.filter((call) => !standaloneCalls.has(call));
392057
+ if (sequenceCalls.length === 0) return;
392058
+ const foldCount = sequenceCalls.length - keepExpanded;
392059
+ if (foldCount <= 0) return;
392022
392060
  const components = children.slice(start);
392023
392061
  const standalone = [];
392024
392062
  const sequenceComponents = [];
392063
+ const keptComponents = [];
392064
+ let remainingToFold = foldCount;
392065
+ let stoppedAtUnfinished = false;
392025
392066
  for (const component of components) {
392026
- if (component instanceof ToolCallSequenceComponent) {
392027
- sequenceComponents.push(...component.children);
392028
- component.clear();
392067
+ if (stoppedAtUnfinished) {
392068
+ keptComponents.push(component);
392029
392069
  continue;
392030
392070
  }
392031
- if (component instanceof ToolCallComponent && standaloneCalls.has(component)) standalone.push(component);
392032
- else sequenceComponents.push(component);
392033
- }
392034
- if (sequenceCalls.length === 0) return;
392035
- const sequence = new ToolCallSequenceComponent(sequenceComponents, sequenceCalls, this.state.toolDisplayMode);
392036
- children.splice(start, components.length, sequence, ...standalone);
392071
+ if (component instanceof ToolCallComponent && standaloneCalls.has(component)) {
392072
+ standalone.push(component);
392073
+ continue;
392074
+ }
392075
+ const calls = toolCallsIn(component);
392076
+ const callCount = calls?.filter((call) => !standaloneCalls.has(call)).length ?? 0;
392077
+ const hasUnfinished = calls?.some((call) => !call.isFinished()) ?? false;
392078
+ if (remainingToFold > 0 && hasUnfinished) {
392079
+ keptComponents.push(component);
392080
+ stoppedAtUnfinished = true;
392081
+ continue;
392082
+ }
392083
+ if (remainingToFold > 0) {
392084
+ if (component instanceof ToolCallSequenceComponent) {
392085
+ sequenceComponents.push(...component.children);
392086
+ component.clear();
392087
+ } else sequenceComponents.push(component);
392088
+ remainingToFold -= callCount;
392089
+ } else keptComponents.push(component);
392090
+ }
392091
+ if (sequenceComponents.length === 0) return;
392092
+ const foldedCalls = [];
392093
+ for (const component of sequenceComponents) {
392094
+ const componentCalls = toolCallsIn(component);
392095
+ if (componentCalls !== void 0) foldedCalls.push(...componentCalls.filter((call) => !standaloneCalls.has(call)));
392096
+ }
392097
+ if (foldedCalls.length === 0) return;
392098
+ const sequence = new ToolCallSequenceComponent(sequenceComponents, foldedCalls, this.state.toolDisplayMode);
392099
+ children.splice(start, components.length, sequence, ...standalone, ...keptComponents);
392037
392100
  this.state.transcriptContainer.invalidate();
392038
392101
  }
392039
392102
  foldCurrentTurnSteps(keepSteps) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dimi-agent/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "The Starting Point for Next-Gen Agents",
5
5
  "keywords": [
6
6
  "agent",
@@ -60,24 +60,24 @@
60
60
  "tsx": "^4.21.0",
61
61
  "yazl": "^3.3.1",
62
62
  "zod": "^4.3.6",
63
- "@dimi-agent/agent-core-v2": "^0.1.0",
64
63
  "@dimi-agent/dimi-oauth": "^0.1.0",
65
64
  "@dimi-agent/dimi-sdk": "^0.2.0",
66
- "@dimi-agent/dimi-telemetry": "^0.1.0",
67
65
  "@dimi-agent/dimi-web": "^0.1.0",
66
+ "@dimi-agent/agent-core-v2": "^0.1.0",
67
+ "@dimi-agent/dimi-telemetry": "^0.1.0",
68
68
  "@dimi-agent/kap-server": "^0.1.0",
69
- "@dimi-agent/remote": "^0.1.0",
70
- "@dimi-agent/pi-tui": "^0.1.0"
69
+ "@dimi-agent/pi-tui": "^0.1.0",
70
+ "@dimi-agent/remote": "^0.1.0"
71
71
  },
72
72
  "optionalDependencies": {
73
- "@mariozechner/clipboard": "^0.3.9",
74
- "node-pty": "^1.1.0",
75
73
  "@dimi-agent/dimi-native-darwin-arm64": ">=0.5.3 <1",
76
74
  "@dimi-agent/dimi-native-darwin-x64": ">=0.5.3 <1",
77
- "@dimi-agent/dimi-native-linux-x64": ">=0.5.3 <1",
78
75
  "@dimi-agent/dimi-native-linux-arm64": ">=0.5.3 <1",
76
+ "@dimi-agent/dimi-native-linux-x64": ">=0.5.3 <1",
77
+ "@dimi-agent/dimi-native-win32-arm64": ">=0.5.3 <1",
79
78
  "@dimi-agent/dimi-native-win32-x64": ">=0.5.3 <1",
80
- "@dimi-agent/dimi-native-win32-arm64": ">=0.5.3 <1"
79
+ "@mariozechner/clipboard": "^0.3.9",
80
+ "node-pty": "^1.1.0"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22.19.0"