@higherdev/cli 0.26.0 → 0.27.0

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
@@ -84,6 +84,10 @@ async function cmdStatus() {
84
84
  if (data.decisions.length > 0) {
85
85
  console.log(`\n${c.yellow(String(data.decisions.length))} open decision${data.decisions.length === 1 ? "" : "s"}`);
86
86
  }
87
+ if (data.chat_reply_median_ms != null) {
88
+ const seconds = Math.max(0, Math.round(data.chat_reply_median_ms / 1_000));
89
+ console.log(`\n${c.bold("Chat replies")} median ${seconds}s (last day)`);
90
+ }
87
91
  }
88
92
  async function cmdTicket(argv, deps = {}) {
89
93
  const [action, ...rest] = argv;
package/dist/tui/App.js CHANGED
@@ -26,7 +26,7 @@ import { bubbleRows } from "./height.js";
26
26
  import { planLayout, splitPanels } from "./layout.js";
27
27
  import { parseLine } from "./parse.js";
28
28
  import { configuredSlugs, acknowledgeInbox, approveEpic, cancelTicket, mergeTicket, createEpicFromFile, decisionOptions, deleteAgent, deleteEpic, followChat, loadChatMessages, loadLiveEvents, listWorkspaceEnv, loadTicketDetail, POLL_MS, pollSnapshot, postAgentMessage, postTicketMessage, queueTicket, resolveDecision, selectDecision, setWorkspacePaused, switchWorkspace, updateAgent, updateProviderCap, updateWorkspace, } from "./data.js";
29
- import { inputActive, NO_REPLY_NOTE, promptPlaceholder, REPLY_WAIT_MS } from "./chat-wait.js";
29
+ import { inputActive, promptPlaceholder, settleChatReply } from "./chat-wait.js";
30
30
  import { answeredLine, decisionHeaderIndex, decisionIdAt, moveDecisionFocus, nextUnanswered, resolveDecisionAnswer, } from "./decide-nav.js";
31
31
  import { EARLIER_PAGE } from "./inbox.js";
32
32
  import { editFor, editableKeys, nextValue, seedFor, settingsRows } from "./settings-model.js";
@@ -412,8 +412,7 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
412
412
  const posted = await postAgentMessage(role, text, config, attachmentIds);
413
413
  if (attachmentIds.length)
414
414
  setPendingAttachments([]);
415
- const deadline = Date.now() + REPLY_WAIT_MS;
416
- while (Date.now() <= deadline) {
415
+ while (true) {
417
416
  const follow = await followChat(config, role, posted.id, since);
418
417
  const activity = pendingActivity(follow.events, follow.run, agentName(role));
419
418
  setThread(key, (current) => ({
@@ -427,10 +426,13 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
427
426
  },
428
427
  }));
429
428
  if (followShouldStop(follow.run, follow.reply)) {
430
- const body = follow.reply ?? follow.run?.summary ?? NO_REPLY_NOTE;
429
+ const settled = settleChatReply(follow.reply, follow.run);
431
430
  setThread(key, (current) => ({
432
431
  ...current,
433
- turns: [...current.turns, { id: `reply-${posted.id}`, speaker: role, body, at: new Date().toISOString() }],
432
+ turns: [...current.turns, {
433
+ id: `reply-${posted.id}`, speaker: role, body: settled.body, at: new Date().toISOString(),
434
+ replyMs: settled.replyMs,
435
+ }],
434
436
  pending: null,
435
437
  }));
436
438
  setChatOffset(10_000);
@@ -438,13 +440,6 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
438
440
  }
439
441
  await new Promise((resolve) => setTimeout(resolve, POLL_MS));
440
442
  }
441
- setThread(key, (current) => ({
442
- ...current,
443
- turns: [...current.turns, {
444
- id: `timeout-${posted.id}`, speaker: role, body: NO_REPLY_NOTE, at: new Date().toISOString(),
445
- }],
446
- pending: null,
447
- }));
448
443
  }
449
444
  catch (error) {
450
445
  const body = error instanceof Error ? error.message : String(error);
@@ -1,3 +1,4 @@
1
+ import { answeredIn, QUEUED_STEP } from "./chat-wait.js";
1
2
  import { narrateEvents } from "./narrate.js";
2
3
  import { wrapLines } from "./ticket-view.js";
3
4
  export function chatInstructionEffects(body) {
@@ -93,7 +94,7 @@ export function pendingActivity(events, run, label, now = Date.now()) {
93
94
  export function followShouldStop(run, reply) {
94
95
  if (reply)
95
96
  return true;
96
- if (run && !["queued", "running"].includes(run.status))
97
+ if (run && ["failed", "killed"].includes(run.status))
97
98
  return true;
98
99
  return false;
99
100
  }
@@ -113,6 +114,9 @@ export function chatViewLines(thread, width, label, now = Date.now()) {
113
114
  const effects = chatInstructionEffects(turn.body);
114
115
  if (effects)
115
116
  lines.push({ key: `${turn.id}:effects`, kind: "status", text: effects });
117
+ if (turn.replyMs != null) {
118
+ lines.push({ key: `${turn.id}:reply-ms`, kind: "status", text: answeredIn(turn.replyMs) });
119
+ }
116
120
  }
117
121
  }
118
122
  if (thread.pending) {
@@ -122,6 +126,9 @@ export function chatViewLines(thread, width, label, now = Date.now()) {
122
126
  : live.status;
123
127
  if (status)
124
128
  lines.push({ key: "pending:status", kind: "status", text: status });
129
+ if (thread.pending.status === "queued") {
130
+ lines.push({ key: "pending:queued", kind: "activity", text: QUEUED_STEP });
131
+ }
125
132
  for (const [index, title] of thread.pending.activity.entries()) {
126
133
  wrapLines(title, bodyWidth).forEach((text, line) => {
127
134
  lines.push({ key: `pending:activity:${index}:${line}`, kind: "activity", text });
@@ -1,6 +1,4 @@
1
- /** How long the TUI waits for an architect or orchestrator reply. */
2
- export const REPLY_WAIT_MS = 180_000;
3
- /** Shown in the pending bubble when the wait expires. The reply can still land in /inbox. */
1
+ /** Shown on the pending bubble only when the chat run failed. The reply can still land in /inbox. */
4
2
  export const NO_REPLY_NOTE = "No reply yet. It will land in /inbox.";
5
3
  export const QUEUED_STEP = "· queued, it answers on the next tick";
6
4
  /**
@@ -13,6 +11,19 @@ export function inputActive(state) {
13
11
  export function promptPlaceholder(state) {
14
12
  return state.busy ? "working…" : "message, or /help";
15
13
  }
16
- export function settleChatReply(reply) {
17
- return { body: reply ?? NO_REPLY_NOTE, pending: false, steps: [], done: true };
14
+ export function failedChatNote(reason) {
15
+ const detail = reason?.trim();
16
+ return detail ? `${NO_REPLY_NOTE} ${detail}` : NO_REPLY_NOTE;
17
+ }
18
+ export function answeredIn(ms) {
19
+ return `answered in ${Math.max(0, Math.round(ms / 1000))}s`;
20
+ }
21
+ export function settleChatReply(reply, run) {
22
+ if (reply) {
23
+ return { body: reply, pending: false, steps: [], done: true, replyMs: run?.reply_ms ?? null };
24
+ }
25
+ if (run && ["failed", "killed"].includes(run.status ?? "")) {
26
+ return { body: failedChatNote(run.summary), pending: false, steps: [], done: true, replyMs: run.reply_ms ?? null };
27
+ }
28
+ return { body: "", pending: true, steps: [], done: false, replyMs: null };
18
29
  }
package/dist/tui/data.js CHANGED
@@ -67,6 +67,7 @@ export async function loadSnapshot(config = loadConfig(), options = {}) {
67
67
  followup: Number(workspaceData.workspace.settings.max_turns?.followup ?? 15),
68
68
  review: Number(workspaceData.workspace.settings.max_turns?.review ?? 50),
69
69
  orchestrate: Number(workspaceData.workspace.settings.max_turns?.orchestrate ?? 30),
70
+ chat: Number(workspaceData.workspace.settings.max_turns?.chat ?? 8),
70
71
  },
71
72
  max_attempts: Number(workspaceData.workspace.settings.max_attempts ?? 3),
72
73
  },
@@ -7,7 +7,7 @@ export function settingsRows(workspace, agents) {
7
7
  { key: "w:default_branch", kind: "text", label: "branch", value: workspace.default_branch },
8
8
  { key: "w:default_host", kind: "text", label: "host", value: workspace.default_host },
9
9
  { key: "w:auto_merge", kind: "toggle", label: "auto merge", value: workspace.auto_merge ? "yes" : "no" },
10
- ...["build", "followup", "review", "orchestrate"].map((kind) => ({
10
+ ...["build", "followup", "review", "orchestrate", "chat"].map((kind) => ({
11
11
  key: `w:max_turns:${kind}`, kind: "number", label: `turns ${kind}`,
12
12
  value: String(workspace.max_turns[kind]), hint: "integer >= 1",
13
13
  })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@higherdev/cli",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",