@higherdev/cli 0.26.0 → 0.28.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 +4 -0
- package/dist/tui/App.js +8 -13
- package/dist/tui/chat-view.js +15 -2
- package/dist/tui/chat-wait.js +26 -5
- package/dist/tui/data.js +2 -1
- package/dist/tui/settings-model.js +1 -1
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
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) => ({
|
|
@@ -426,11 +425,14 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
|
|
|
426
425
|
activity: activity.lines,
|
|
427
426
|
},
|
|
428
427
|
}));
|
|
429
|
-
if (followShouldStop(follow.run, follow.reply)) {
|
|
430
|
-
const
|
|
428
|
+
if (followShouldStop(follow.run, follow.reply, follow.tagged)) {
|
|
429
|
+
const settled = settleChatReply(follow.reply, follow.run);
|
|
431
430
|
setThread(key, (current) => ({
|
|
432
431
|
...current,
|
|
433
|
-
turns: [...current.turns, {
|
|
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);
|
package/dist/tui/chat-view.js
CHANGED
|
@@ -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) {
|
|
@@ -90,10 +91,16 @@ export function pendingActivity(events, run, label, now = Date.now()) {
|
|
|
90
91
|
lines: narrateEvents(events).map((line) => line.title),
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
|
-
export function followShouldStop(run, reply) {
|
|
94
|
+
export function followShouldStop(run, reply, tagged = false) {
|
|
94
95
|
if (reply)
|
|
95
96
|
return true;
|
|
96
|
-
if (
|
|
97
|
+
if (!run)
|
|
98
|
+
return false;
|
|
99
|
+
if (["failed", "killed"].includes(run.status))
|
|
100
|
+
return true;
|
|
101
|
+
// The role's other runs may finish while ours is still queued; only the run
|
|
102
|
+
// started for this message is definitive when it ends without a reply.
|
|
103
|
+
if (tagged && !["queued", "running"].includes(run.status))
|
|
97
104
|
return true;
|
|
98
105
|
return false;
|
|
99
106
|
}
|
|
@@ -113,6 +120,9 @@ export function chatViewLines(thread, width, label, now = Date.now()) {
|
|
|
113
120
|
const effects = chatInstructionEffects(turn.body);
|
|
114
121
|
if (effects)
|
|
115
122
|
lines.push({ key: `${turn.id}:effects`, kind: "status", text: effects });
|
|
123
|
+
if (turn.replyMs != null) {
|
|
124
|
+
lines.push({ key: `${turn.id}:reply-ms`, kind: "status", text: answeredIn(turn.replyMs) });
|
|
125
|
+
}
|
|
116
126
|
}
|
|
117
127
|
}
|
|
118
128
|
if (thread.pending) {
|
|
@@ -122,6 +132,9 @@ export function chatViewLines(thread, width, label, now = Date.now()) {
|
|
|
122
132
|
: live.status;
|
|
123
133
|
if (status)
|
|
124
134
|
lines.push({ key: "pending:status", kind: "status", text: status });
|
|
135
|
+
if (thread.pending.status === "queued") {
|
|
136
|
+
lines.push({ key: "pending:queued", kind: "activity", text: QUEUED_STEP });
|
|
137
|
+
}
|
|
125
138
|
for (const [index, title] of thread.pending.activity.entries()) {
|
|
126
139
|
wrapLines(title, bodyWidth).forEach((text, line) => {
|
|
127
140
|
lines.push({ key: `pending:activity:${index}:${line}`, kind: "activity", text });
|
package/dist/tui/chat-wait.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/**
|
|
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,29 @@ export function inputActive(state) {
|
|
|
13
11
|
export function promptPlaceholder(state) {
|
|
14
12
|
return state.busy ? "working…" : "message, or /help";
|
|
15
13
|
}
|
|
16
|
-
export function
|
|
17
|
-
|
|
14
|
+
export function failedChatNote(reason) {
|
|
15
|
+
const detail = reason?.trim();
|
|
16
|
+
return detail ? `${NO_REPLY_NOTE} ${detail}` : NO_REPLY_NOTE;
|
|
17
|
+
}
|
|
18
|
+
/** A run that finished without writing a reply message: show what it did instead. */
|
|
19
|
+
export const NO_REPLY_WRITTEN = "Finished without a reply.";
|
|
20
|
+
export function finishedWithoutReplyNote(summary) {
|
|
21
|
+
const detail = summary?.trim();
|
|
22
|
+
return detail ? `${NO_REPLY_WRITTEN} ${detail}` : NO_REPLY_WRITTEN;
|
|
23
|
+
}
|
|
24
|
+
export function answeredIn(ms) {
|
|
25
|
+
return `answered in ${Math.max(0, Math.round(ms / 1000))}s`;
|
|
26
|
+
}
|
|
27
|
+
export function settleChatReply(reply, run) {
|
|
28
|
+
if (reply) {
|
|
29
|
+
return { body: reply, pending: false, steps: [], done: true, replyMs: run?.reply_ms ?? null };
|
|
30
|
+
}
|
|
31
|
+
if (run && ["failed", "killed"].includes(run.status ?? "")) {
|
|
32
|
+
return { body: failedChatNote(run.summary), pending: false, steps: [], done: true, replyMs: run.reply_ms ?? null };
|
|
33
|
+
}
|
|
34
|
+
if (run && run.status && !["queued", "running"].includes(run.status)) {
|
|
35
|
+
return { body: finishedWithoutReplyNote(run.summary), pending: false, steps: [], done: true,
|
|
36
|
+
replyMs: run.reply_ms ?? null };
|
|
37
|
+
}
|
|
38
|
+
return { body: "", pending: true, steps: [], done: false, replyMs: null };
|
|
18
39
|
}
|
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
|
},
|
|
@@ -155,7 +156,7 @@ export async function followChat(config, role, messageId, since) {
|
|
|
155
156
|
const reply = messages.find((message) => message.from_role === role
|
|
156
157
|
&& ["human", "operator", "all"].includes(message.to_role)
|
|
157
158
|
&& (!run || !message.run_id || message.run_id === run.id));
|
|
158
|
-
return { run, events, reply: reply?.body_md ?? null };
|
|
159
|
+
return { run, events, reply: reply?.body_md ?? null, tagged: Boolean(tagged) };
|
|
159
160
|
}
|
|
160
161
|
export async function loadTicketDetail(config, key) {
|
|
161
162
|
const { ticket, pr, events, runs, messages, decisions, attachments } = await showTicket(key, config);
|
|
@@ -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
|
})),
|