@nanhara/hara 0.98.2 → 0.98.4

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/dist/index.js CHANGED
@@ -2743,6 +2743,7 @@ program.action(async (opts) => {
2743
2743
  orgId: __activeP.kind === "gateway" ? __activeP.deviceId || __activeP.id : undefined,
2744
2744
  routeHost: __routeForHeader?.host,
2745
2745
  modelSource: __modelSource,
2746
+ visionModel: cfg.visionModel,
2746
2747
  },
2747
2748
  visionNotice: __visionNotice,
2748
2749
  cycleApproval: (m) => cycleMode(m),
@@ -3118,7 +3119,11 @@ program.action(async (opts) => {
3118
3119
  await maybeAutoCompact(provider, history, meta, stats, cfg, (m) => h.sink.notice(m));
3119
3120
  },
3120
3121
  });
3121
- out("\n" + c.dim("Session ") + c.bold(shortId(meta.id)) + c.dim(" saved · resume: ") + c.cyan(`hara resume ${shortId(meta.id)}`) + "\n");
3122
+ // Only claim "saved · resume" if a turn actually persisted the session. A zero-turn session (opened,
3123
+ // then exited without submitting anything) is never written by saveSession — so printing the resume
3124
+ // hint would mislead and `hara resume <id>` would fail with "no session matching".
3125
+ if (loadSession(meta.id))
3126
+ out("\n" + c.dim("Session ") + c.bold(shortId(meta.id)) + c.dim(" saved · resume: ") + c.cyan(`hara resume ${shortId(meta.id)}`) + "\n");
3122
3127
  await closeMcp();
3123
3128
  process.exit(0); // TUI done — exit cleanly (ink can leave stdin referenced)
3124
3129
  }
package/dist/tui/App.js CHANGED
@@ -165,6 +165,14 @@ export function shortenSession(uuid) {
165
165
  * pad a label so the values align vertically across rows. */
166
166
  const FIELD_PAD = 10;
167
167
  const padField = (name) => name.padEnd(FIELD_PAD, " ");
168
+ /** Dim trailing clause for the model line: the vision sidecar (only when configured) then a subtle
169
+ * codex-style `/model` hint. Pure so the composition (spacing, ordering, silence-when-unset) can be
170
+ * pinned in a unit test without rendering React. Returns "" when there is nothing to show beyond
171
+ * the hint — the hint itself is always present (it's the discoverability affordance). */
172
+ export function modelLineSuffix(visionModel) {
173
+ const vision = visionModel ? ` · vision ${visionModel}` : "";
174
+ return `${vision} · /model to change`;
175
+ }
168
176
  function HeaderCard(props) {
169
177
  const { version, modelLabel, cwd, agentsMdLoaded, session, kind } = props;
170
178
  const home = process.env.HOME ?? "";
@@ -172,8 +180,8 @@ function HeaderCard(props) {
172
180
  const sessionShort = shortenSession(session);
173
181
  // Identity line — branches on kind. Personal collapses kind+model into one line;
174
182
  // org splits identity (org/label/id/route) from model (with its source).
175
- const identity = kind === "org" ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("org")}` }), _jsx(Text, { children: props.orgLabel ?? props.orgId ?? "(unnamed)" }), props.orgId && props.orgLabel ? _jsx(Text, { dimColor: true, children: ` · ${props.orgId}` }) : null, props.routeHost ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: " → " }), _jsx(Text, { dimColor: true, children: props.routeHost })] })) : null] })) : (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField(props.profileId ? `personal:${props.profileId}` : "personal")}` }), _jsx(Text, { children: modelLabel }), props.routeHost ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: " → " }), _jsx(Text, { dimColor: true, children: props.routeHost })] })) : null] }));
176
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: accent(), children: "> " }), _jsx(Text, { color: accent(), bold: true, children: `hara` }), _jsx(Text, { dimColor: true, children: ` · v${version} — the coding agent that runs like an org` })] }), _jsx(Text, { children: " " }), identity, kind === "org" ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("model")}` }), _jsx(Text, { children: modelLabel }), props.modelSource ? _jsx(Text, { dimColor: true, children: ` · from ${props.modelSource}` }) : null] })) : null, _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("cwd")}` }), _jsx(Text, { dimColor: true, children: cwdShort }), agentsMdLoaded ? _jsx(Text, { dimColor: true, children: " · AGENTS.md" }) : null] }), sessionShort ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("session")}` }), _jsx(Text, { dimColor: true, children: sessionShort })] })) : null, _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " /help · @file · shift+tab · esc" })] }));
183
+ const identity = kind === "org" ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("org")}` }), _jsx(Text, { children: props.orgLabel ?? props.orgId ?? "(unnamed)" }), props.orgId && props.orgLabel ? _jsx(Text, { dimColor: true, children: ` · ${props.orgId}` }) : null, props.routeHost ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: " → " }), _jsx(Text, { dimColor: true, children: props.routeHost })] })) : null] })) : (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField(props.profileId ? `personal:${props.profileId}` : "personal")}` }), _jsx(Text, { children: modelLabel }), props.routeHost ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: " → " }), _jsx(Text, { dimColor: true, children: props.routeHost })] })) : null, _jsx(Text, { dimColor: true, children: modelLineSuffix(props.visionModel) })] }));
184
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: accent(), children: "> " }), _jsx(Text, { color: accent(), bold: true, children: `hara` }), _jsx(Text, { dimColor: true, children: ` · v${version} — the coding agent that runs like an org` })] }), _jsx(Text, { children: " " }), identity, kind === "org" ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("model")}` }), _jsx(Text, { children: modelLabel }), props.modelSource ? _jsx(Text, { dimColor: true, children: ` · from ${props.modelSource}` }) : null, _jsx(Text, { dimColor: true, children: modelLineSuffix(props.visionModel) })] })) : null, _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("cwd")}` }), _jsx(Text, { dimColor: true, children: cwdShort }), agentsMdLoaded ? _jsx(Text, { dimColor: true, children: " · AGENTS.md" }) : null] }), sessionShort ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ` ${padField("session")}` }), _jsx(Text, { dimColor: true, children: sessionShort })] })) : null, _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " /help · @file · shift+tab · esc · ctrl+t transcript · ctrl+r reasoning" })] }));
177
185
  }
178
186
  // Spinner verb: while a turn is running, prefer the in_progress todo's activeForm (or its text),
179
187
  // so the bottom line reads "▶ updating tests…" instead of an abstract "working". Falls back to
@@ -31,7 +31,9 @@ const TopBorder = memo(function TopBorder({ name, width }) {
31
31
  });
32
32
  // Bottom border carries token usage + concurrency at the right corner (modes moved to ModeBar below).
33
33
  const BottomBorder = memo(function BottomBorder({ s, width }) {
34
- const usage = `↑${tok(s.input)} ↓${tok(s.output)}${s.ctxPct > 0 ? ` · ctx ${s.ctxPct}%` : ""}`;
34
+ // Always render `ctx N%` (from 0 on) so the field is present from the first frame — otherwise it
35
+ // pops in mid-session the moment ctx first exceeds 0, shifting the whole bottom-border layout.
36
+ const usage = `↑${tok(s.input)} ↓${tok(s.output)} · ctx ${s.ctxPct}%`;
35
37
  const label = s.agents > 0 ? `${usage} · ⛁${s.agents}` : `${usage} · ⛁ idle`;
36
38
  const left = Math.max(2, width - label.length - 3);
37
39
  return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${"─".repeat(left)} ${label} ─` }) }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanhara/hara",
3
- "version": "0.98.2",
3
+ "version": "0.98.4",
4
4
  "description": "hara — a coding agent CLI that runs like an engineering org.",
5
5
  "bin": {
6
6
  "hara": "dist/index.js"