my-durable 0.0.17 → 0.0.18

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.
@@ -12,7 +12,7 @@ import {
12
12
  type OverlayHandle,
13
13
  type TuiInputListenerResult,
14
14
  } from "@earendil-works/pi-tui";
15
- import { DpiClient, follow, type AgentHandle, type ConnectionState } from "../../dpi-sdk/src/client.ts";
15
+ import { DpiClient, follow, type AgentHandle, type ConnectionState, type GitStatus } from "../../dpi-sdk/src/client.ts";
16
16
  import { isDpiError } from "../../dpi-sdk/src/errors.ts";
17
17
  import { AgentModel, SessionModel } from "../../dpi-sdk/src/models.ts";
18
18
  import { sumUsage } from "../../dpi-sdk/src/transcript.ts";
@@ -152,6 +152,26 @@ export class App {
152
152
  private overlays: Array<{ handle: OverlayHandle; onClose?: () => void }> = [];
153
153
  private ticker?: ReturnType<typeof setInterval>;
154
154
  private sessions: SessionSummary[] = [];
155
+ /** Ambient git for the panel: cwd → {at, status}, 30s TTL, fetched as the
156
+ * cursor moves — the herd and agent header always show ⎇ branch ±dirty. */
157
+ private gitCache = new Map<string, { at: number; s: GitStatus }>();
158
+
159
+ private refreshGit(cwd: string | undefined) {
160
+ if (!cwd) return;
161
+ const hit = this.gitCache.get(cwd);
162
+ if (hit && Date.now() - hit.at < 30_000) return;
163
+ void this.client
164
+ .gitStatus(cwd)
165
+ .then((s) => {
166
+ this.gitCache.set(cwd, { at: Date.now(), s });
167
+ this.requestRender();
168
+ })
169
+ .catch(() => {});
170
+ }
171
+
172
+ private gitOf(cwd: string | undefined): GitStatus | undefined {
173
+ return cwd ? this.gitCache.get(cwd)?.s : undefined;
174
+ }
155
175
  private devicesCache: DeviceRow[] = [];
156
176
  private deviceOnline = false;
157
177
  private events: { at: string; glyph: string; text: string }[] = [];
@@ -972,7 +992,8 @@ export class App {
972
992
  }).slice(0, h);
973
993
  if (!wide) return table;
974
994
  const selected = this.sessions.find((s) => s.id === (this.selectedId ?? this.sessionId));
975
- const panel = renderHerdPanel(panelW, h, selected, this.contextLeft(), this.device.name);
995
+ if (selected?.cwd) this.refreshGit(selected.cwd);
996
+ const panel = renderHerdPanel(panelW, h, selected, this.contextLeft(), this.device.name, this.gitOf(selected?.cwd));
976
997
  return joinPanel(table, panel, tableW, w, h);
977
998
  }
978
999
 
@@ -1000,8 +1021,11 @@ export class App {
1000
1021
  const ctx = this.contextLeft();
1001
1022
  const barW = Math.min(40, Math.max(12, Math.round(w * 0.3)));
1002
1023
  const filled = ctx === undefined ? 0 : Math.round((ctx / 100) * barW);
1024
+ const g = this.gitOf(session?.cwd);
1025
+ if (session?.cwd) this.refreshGit(session.cwd);
1026
+ const gitChip = g?.repo ? ` ${c.muted("⎇")} ${c.text(g.branch ?? g.head ?? "")}${(g.staged ?? 0) + (g.unstaged ?? 0) + (g.untracked ?? 0) ? c.warn(` ±${(g.staged ?? 0) + (g.unstaged ?? 0) + (g.untracked ?? 0)}`) : ""}` : "";
1003
1027
  lines.push(
1004
- `${c.muted(this.device.id)} ${c.muted("›")} ${c.text(session?.name ?? "—")} ${c.muted("›")} ${c.bold(`#${this.agentId ?? 1}`)} ` +
1028
+ `${c.muted(this.device.id)} ${c.muted("›")} ${c.text(session?.name ?? "—")} ${c.muted("›")} ${c.bold(`#${this.agentId ?? 1}`)}${gitChip} ` +
1005
1029
  `${STATE_COLOR[state](STATE_GLYPH[state] + " " + (this.busy() ? "working" : "idle"))} ` +
1006
1030
  `${c.muted("ctx")} ${ctx === undefined ? c.muted("—") : c.accent(rep("█", filled)) + c.muted(rep("░", barW - filled)) + " " + c.muted(`${ctx}%`)}`,
1007
1031
  );
@@ -285,6 +285,7 @@ export function renderHerdPanel(
285
285
  s: SessionSummary | undefined,
286
286
  ctxLeft?: number,
287
287
  device?: string,
288
+ git?: { repo: boolean; branch?: string; head?: string; staged?: number; unstaged?: number; untracked?: number; ahead?: number; behind?: number; worktreeOf?: string },
288
289
  ): string[] {
289
290
  // No session selected: a blank panel next to the divider is what made the
290
291
  // wide layout look broken — herdr's panel always carries a device header.
@@ -308,6 +309,9 @@ export function renderHerdPanel(
308
309
  c.muted(" OVERVIEW"),
309
310
  ` ${c.bold(s.name)}`,
310
311
  ` ${c.muted(basename(s.cwd))} ${c.muted("·")} ${c.text(basename(s.model) || s.model)}`,
312
+ git?.repo
313
+ ? ` ${c.muted("⎇")} ${c.text(git.branch ?? git.head ?? "?")}${c.muted("±")}${c.text(String((git.staged ?? 0) + (git.unstaged ?? 0) + (git.untracked ?? 0)))}${git.ahead ? c.text(` ↑${git.ahead}`) : ""}${git.behind ? c.text(` ↓${git.behind}`) : ""}${git.worktreeOf ? c.muted(" · worktree") : ""}`
314
+ : "",
311
315
  "",
312
316
  ` ${c.muted("state")} ${STATE_COLOR[sessionState(s)](sessionState(s))}`,
313
317
  ` ${c.muted("agents")} ${agents.length ? agents[0] : "—"}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-durable",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Durable Pi \u2014 the durable agent network: hub web UI, device backend daemon, TUI admin and the shared SDK, packaged as one runnable monorepo install",
5
5
  "license": "MIT",
6
6
  "repository": {