@hydra-acp/cli 0.1.41 → 0.1.43

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/cli.js CHANGED
@@ -985,7 +985,8 @@ function mergeMeta(passthrough, ours) {
985
985
  function sessionListEntryToWire(entry) {
986
986
  const hydraMeta = {
987
987
  attachedClients: entry.attachedClients,
988
- status: entry.status
988
+ status: entry.status,
989
+ busy: entry.busy
989
990
  };
990
991
  if (entry.agentId !== void 0) {
991
992
  hydraMeta.agentId = entry.agentId;
@@ -1131,6 +1132,10 @@ var init_types = __esm({
1131
1132
  updatedAt: z3.string(),
1132
1133
  attachedClients: z3.number().int().nonnegative(),
1133
1134
  status: z3.enum(["live", "cold"]).default("live"),
1135
+ // True while the session is mid-turn (an agent prompt is in flight).
1136
+ // Always false for cold sessions. Lets pickers render a busy dot
1137
+ // without having to attach.
1138
+ busy: z3.boolean().default(false),
1134
1139
  _meta: z3.record(z3.unknown()).optional()
1135
1140
  });
1136
1141
  SessionListEntryWire = z3.object({
@@ -4449,7 +4454,8 @@ async function listSessions(target, opts = {}, fetchImpl = fetch) {
4449
4454
  currentUsage: s.currentUsage,
4450
4455
  title: s.title,
4451
4456
  importedFromMachine: s.importedFromMachine,
4452
- importedFromUpstreamSessionId: s.importedFromUpstreamSessionId
4457
+ importedFromUpstreamSessionId: s.importedFromUpstreamSessionId,
4458
+ busy: s.busy
4453
4459
  }));
4454
4460
  }
4455
4461
  async function killSession(target, id, fetchImpl = fetch) {
@@ -4573,7 +4579,7 @@ function toRow(s, now = Date.now()) {
4573
4579
  return {
4574
4580
  session: stripHydraSessionPrefix(s.sessionId),
4575
4581
  upstream: formatUpstreamCell(s.upstreamSessionId, s.importedFromMachine),
4576
- state: formatState(s.status, s.attachedClients),
4582
+ state: formatState(s.status, s.busy),
4577
4583
  agent: formatAgentCell(s.agentId, s.currentUsage),
4578
4584
  age: formatRelativeAge(s.updatedAt, now),
4579
4585
  title: s.title ?? "-",
@@ -4589,11 +4595,11 @@ function formatUpstreamCell(upstreamSessionId, importedFromMachine) {
4589
4595
  }
4590
4596
  return "-";
4591
4597
  }
4592
- function formatState(status, clients) {
4598
+ function formatState(status, busy) {
4593
4599
  if (status === "cold") {
4594
4600
  return "COLD";
4595
4601
  }
4596
- return `LIVE(${clients})`;
4602
+ return busy ? "LIVE\u2022" : "LIVE";
4597
4603
  }
4598
4604
  function computeWidths(rows) {
4599
4605
  return {
@@ -6158,7 +6164,7 @@ function writeStyled(term, text, style) {
6158
6164
  term(text);
6159
6165
  return;
6160
6166
  case "thought":
6161
- term.brightBlack.dim.noFormat(text);
6167
+ term.brightBlack.noFormat(text);
6162
6168
  return;
6163
6169
  case "tool":
6164
6170
  term.brightBlue.noFormat(text);
@@ -7029,6 +7035,10 @@ uncaught: ${err.stack ?? err.message}
7029
7035
  this.handleCsi27Stdin(text);
7030
7036
  return;
7031
7037
  }
7038
+ if (text.includes("\x1B[200~")) {
7039
+ this.handleRawStdinSegment(text);
7040
+ return;
7041
+ }
7032
7042
  if (text.includes("\n")) {
7033
7043
  const parts = text.split("\n");
7034
7044
  for (let i = 0; i < parts.length; i++) {
@@ -8793,7 +8803,7 @@ async function pickSession(term, opts) {
8793
8803
  if (tier !== 0) {
8794
8804
  return tier;
8795
8805
  }
8796
- return b.updatedAt.localeCompare(a.updatedAt);
8806
+ return b.updatedAt.slice(0, 16).localeCompare(a.updatedAt.slice(0, 16));
8797
8807
  });
8798
8808
  };
8799
8809
  let cwdOnly = false;
@@ -9145,6 +9155,27 @@ async function pickSession(term, opts) {
9145
9155
  paintIndicator();
9146
9156
  });
9147
9157
  };
9158
+ const repaintDataZone = () => {
9159
+ withSync(() => {
9160
+ term.moveTo(1, headerRow());
9161
+ term.dim.noFormat(` ${headerLine}`);
9162
+ for (let v = 0; v < viewportSize; v++) {
9163
+ const row = headerRow() + 1 + v;
9164
+ const sessionIdx = scrollOffset + v;
9165
+ if (sessionIdx < visible.length) {
9166
+ term.moveTo(1, row);
9167
+ paintSessionRow(sessionIdx);
9168
+ } else {
9169
+ term.moveTo(1, row).eraseLineAfter();
9170
+ }
9171
+ }
9172
+ paintIndicator();
9173
+ if (selectedIdx === 0) {
9174
+ placeComposerCursor();
9175
+ term.hideCursor(false);
9176
+ }
9177
+ });
9178
+ };
9148
9179
  let pasteActive = false;
9149
9180
  let pasteBuffer = "";
9150
9181
  let tkStdinHandler = null;
@@ -9200,6 +9231,8 @@ async function pickSession(term, opts) {
9200
9231
  renderFromScratch();
9201
9232
  return await new Promise((resolve6) => {
9202
9233
  let resolved = false;
9234
+ let autoRefreshTimer = null;
9235
+ let autoRefreshInFlight = false;
9203
9236
  const onResize = () => {
9204
9237
  if (resolved) {
9205
9238
  return;
@@ -9211,6 +9244,10 @@ async function pickSession(term, opts) {
9211
9244
  return;
9212
9245
  }
9213
9246
  resolved = true;
9247
+ if (autoRefreshTimer) {
9248
+ clearInterval(autoRefreshTimer);
9249
+ autoRefreshTimer = null;
9250
+ }
9214
9251
  term.off("key", onKey);
9215
9252
  term.off("resize", onResize);
9216
9253
  process.stdout.write("\x1B[?2004l");
@@ -9227,8 +9264,17 @@ async function pickSession(term, opts) {
9227
9264
  term.moveTo(1, indicatorRow() + 1);
9228
9265
  term("\n");
9229
9266
  };
9230
- const refresh = async (preferredId) => {
9267
+ const renderFingerprint = () => {
9268
+ const cells = rows.map(
9269
+ (r) => `${r.session}|${r.upstream}|${r.state}|${r.agent}|${r.age}|${r.title}|${r.cwd}`
9270
+ ).join("\n");
9271
+ return `${selectedIdx}:${scrollOffset}:${transientStatus ?? ""}
9272
+ ${cells}`;
9273
+ };
9274
+ const refresh = async (preferredId, refreshOpts = {}) => {
9231
9275
  try {
9276
+ const beforeKey = refreshOpts.silent ? renderFingerprint() : "";
9277
+ const beforeTotal = total;
9232
9278
  const next = await listSessions(opts.target);
9233
9279
  allSessions = sortSessions(next);
9234
9280
  applyFilter();
@@ -9245,8 +9291,18 @@ async function pickSession(term, opts) {
9245
9291
  scrollOffset = Math.max(0, visible.length - viewportSize);
9246
9292
  }
9247
9293
  adjustScroll();
9248
- renderFromScratch();
9294
+ if (refreshOpts.silent && renderFingerprint() === beforeKey) {
9295
+ return;
9296
+ }
9297
+ if (total === beforeTotal) {
9298
+ repaintDataZone();
9299
+ } else {
9300
+ renderFromScratch();
9301
+ }
9249
9302
  } catch (err) {
9303
+ if (refreshOpts.silent) {
9304
+ return;
9305
+ }
9250
9306
  transientStatus = `refresh failed: ${err.message}`;
9251
9307
  renderFromScratch();
9252
9308
  }
@@ -9723,6 +9779,16 @@ async function pickSession(term, opts) {
9723
9779
  }
9724
9780
  term.on("key", onKey);
9725
9781
  term.on("resize", onResize);
9782
+ autoRefreshTimer = setInterval(() => {
9783
+ if (resolved || mode !== "normal" || searchActive || autoRefreshInFlight) {
9784
+ return;
9785
+ }
9786
+ const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
9787
+ autoRefreshInFlight = true;
9788
+ void refresh(currentId, { silent: true }).finally(() => {
9789
+ autoRefreshInFlight = false;
9790
+ });
9791
+ }, 3e3);
9726
9792
  });
9727
9793
  }
9728
9794
  function readTermHeight(term) {
@@ -11161,9 +11227,38 @@ async function runTuiApp(opts) {
11161
11227
  const viewPrefs = {
11162
11228
  showThoughts: config.tui.showThoughts
11163
11229
  };
11230
+ let altScreenEngaged = false;
11231
+ const enterAltScreen = () => {
11232
+ if (altScreenEngaged) {
11233
+ return;
11234
+ }
11235
+ term.fullscreen(true);
11236
+ altScreenEngaged = true;
11237
+ };
11238
+ const leaveAltScreen = () => {
11239
+ if (!altScreenEngaged) {
11240
+ return;
11241
+ }
11242
+ term.fullscreen(false);
11243
+ altScreenEngaged = false;
11244
+ process.stdout.write("\n");
11245
+ };
11246
+ enterAltScreen();
11247
+ const altScreenCleanup = () => {
11248
+ if (altScreenEngaged) {
11249
+ term.fullscreen(false);
11250
+ altScreenEngaged = false;
11251
+ }
11252
+ };
11253
+ process.once("exit", altScreenCleanup);
11164
11254
  let nextOpts = opts;
11165
- while (nextOpts !== null) {
11166
- nextOpts = await runSession(term, config, target, nextOpts, exitHint, viewPrefs);
11255
+ try {
11256
+ while (nextOpts !== null) {
11257
+ nextOpts = await runSession(term, config, target, nextOpts, exitHint, viewPrefs);
11258
+ }
11259
+ } finally {
11260
+ leaveAltScreen();
11261
+ process.off("exit", altScreenCleanup);
11167
11262
  }
11168
11263
  const pendingUpdate = await getPendingUpdate();
11169
11264
  if (pendingUpdate) {
@@ -11173,7 +11268,6 @@ async function runTuiApp(opts) {
11173
11268
  if (exitHint.sessionId && process.stdout.isTTY) {
11174
11269
  const short = stripHydraSessionPrefix(exitHint.sessionId);
11175
11270
  const flags = exitHint.readonly ? " --readonly" : "";
11176
- process.stdout.write("\x1B[2J\x1B[H");
11177
11271
  process.stdout.write(
11178
11272
  `To resume: ${invokedBinName()} tui --session ${short}${flags}
11179
11273
  `
@@ -11184,7 +11278,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
11184
11278
  const ctx = await resolveSession(term, config, target, opts);
11185
11279
  if (!ctx) {
11186
11280
  term.grabInput(false);
11187
- process.exit(0);
11281
+ return null;
11188
11282
  }
11189
11283
  const launchLabelBase = ctx.sessionId === "__new__" ? "Starting new session\u2026" : "Resuming session\u2026";
11190
11284
  const installStatus = createInstallStatusLine(term, launchLabelBase);
@@ -11917,7 +12011,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
11917
12011
  const sessionbarAgent = resolvedAgentId || agentInfoName || "?";
11918
12012
  const usage = { ...initialUsage ?? {} };
11919
12013
  installStatus.finalize();
11920
- screen.start();
12014
+ screen.start({ skipFullscreen: true });
11921
12015
  screen.setHideThoughts(!viewPrefs.showThoughts);
11922
12016
  screen.setSessionbar({
11923
12017
  agent: sessionbarAgent,
@@ -12065,7 +12159,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
12065
12159
  sessionElapsedTimer = null;
12066
12160
  }
12067
12161
  screen.clearWindowTitle();
12068
- screen.stop();
12162
+ screen.stop({ keepFullscreen: true });
12069
12163
  saveHistory(historyFile, history).catch(() => void 0);
12070
12164
  void stream.close().catch(() => void 0);
12071
12165
  };
@@ -15692,7 +15786,8 @@ var SessionManager = class {
15692
15786
  currentUsage: session.currentUsage,
15693
15787
  updatedAt: used,
15694
15788
  attachedClients: session.attachedCount,
15695
- status: "live"
15789
+ status: "live",
15790
+ busy: session.turnStartedAt !== void 0
15696
15791
  });
15697
15792
  }
15698
15793
  const records = await this.store.list().catch(() => []);
@@ -15716,7 +15811,8 @@ var SessionManager = class {
15716
15811
  importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
15717
15812
  updatedAt: used,
15718
15813
  attachedClients: 0,
15719
- status: "cold"
15814
+ status: "cold",
15815
+ busy: false
15720
15816
  });
15721
15817
  }
15722
15818
  entries.sort((a, b) => a.updatedAt < b.updatedAt ? 1 : -1);
package/dist/index.d.ts CHANGED
@@ -1415,6 +1415,7 @@ declare const SessionListEntry: z.ZodObject<{
1415
1415
  updatedAt: z.ZodString;
1416
1416
  attachedClients: z.ZodNumber;
1417
1417
  status: z.ZodDefault<z.ZodEnum<["live", "cold"]>>;
1418
+ busy: z.ZodDefault<z.ZodBoolean>;
1418
1419
  _meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1419
1420
  }, "strip", z.ZodTypeAny, {
1420
1421
  sessionId: string;
@@ -1422,6 +1423,7 @@ declare const SessionListEntry: z.ZodObject<{
1422
1423
  status: "live" | "cold";
1423
1424
  updatedAt: string;
1424
1425
  attachedClients: number;
1426
+ busy: boolean;
1425
1427
  agentId?: string | undefined;
1426
1428
  upstreamSessionId?: string | undefined;
1427
1429
  title?: string | undefined;
@@ -1454,6 +1456,7 @@ declare const SessionListEntry: z.ZodObject<{
1454
1456
  } | undefined;
1455
1457
  importedFromMachine?: string | undefined;
1456
1458
  importedFromUpstreamSessionId?: string | undefined;
1459
+ busy?: boolean | undefined;
1457
1460
  }>;
1458
1461
  type SessionListEntry = z.infer<typeof SessionListEntry>;
1459
1462
  declare const SessionListResult: z.ZodObject<{
package/dist/index.js CHANGED
@@ -1388,6 +1388,10 @@ var SessionListEntry = z3.object({
1388
1388
  updatedAt: z3.string(),
1389
1389
  attachedClients: z3.number().int().nonnegative(),
1390
1390
  status: z3.enum(["live", "cold"]).default("live"),
1391
+ // True while the session is mid-turn (an agent prompt is in flight).
1392
+ // Always false for cold sessions. Lets pickers render a busy dot
1393
+ // without having to attach.
1394
+ busy: z3.boolean().default(false),
1391
1395
  _meta: z3.record(z3.unknown()).optional()
1392
1396
  });
1393
1397
  var SessionListEntryWire = z3.object({
@@ -1404,7 +1408,8 @@ var SessionListResult = z3.object({
1404
1408
  function sessionListEntryToWire(entry) {
1405
1409
  const hydraMeta = {
1406
1410
  attachedClients: entry.attachedClients,
1407
- status: entry.status
1411
+ status: entry.status,
1412
+ busy: entry.busy
1408
1413
  };
1409
1414
  if (entry.agentId !== void 0) {
1410
1415
  hydraMeta.agentId = entry.agentId;
@@ -5307,7 +5312,8 @@ var SessionManager = class {
5307
5312
  currentUsage: session.currentUsage,
5308
5313
  updatedAt: used,
5309
5314
  attachedClients: session.attachedCount,
5310
- status: "live"
5315
+ status: "live",
5316
+ busy: session.turnStartedAt !== void 0
5311
5317
  });
5312
5318
  }
5313
5319
  const records = await this.store.list().catch(() => []);
@@ -5331,7 +5337,8 @@ var SessionManager = class {
5331
5337
  importedFromUpstreamSessionId: r.importedFromUpstreamSessionId,
5332
5338
  updatedAt: used,
5333
5339
  attachedClients: 0,
5334
- status: "cold"
5340
+ status: "cold",
5341
+ busy: false
5335
5342
  });
5336
5343
  }
5337
5344
  entries.sort((a, b) => a.updatedAt < b.updatedAt ? 1 : -1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "Multi-client ACP session daemon: spawn agents, attach over WSS, multiplex sessions across editors.",
5
5
  "license": "MIT",
6
6
  "type": "module",