@higherdev/cli 0.28.0 → 0.29.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/api.js +3 -0
- package/dist/index.js +32 -3
- package/dist/out.js +1 -0
- package/dist/tui/App.js +21 -3
- package/dist/tui/Help.js +1 -1
- package/dist/tui/chat-view.js +1 -1
- package/dist/tui/chat-wait.js +1 -1
- package/dist/tui/data.js +5 -1
- package/dist/tui/parse.js +3 -1
- package/dist/tui/settings-model.js +7 -0
- package/dist/tui/stream.js +1 -1
- package/dist/workspace-commands.js +6 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -81,6 +81,9 @@ export async function postMessage(fields, config = loadConfig()) {
|
|
|
81
81
|
export async function getRun(id, config = loadConfig()) {
|
|
82
82
|
return request(config, "GET", `/api/w/${config.slug}/runs/${encodeURIComponent(id)}`);
|
|
83
83
|
}
|
|
84
|
+
export async function cancelRun(id, config = loadConfig()) {
|
|
85
|
+
return request(config, "POST", `/api/w/${config.slug}/runs/${encodeURIComponent(id)}/cancel`);
|
|
86
|
+
}
|
|
84
87
|
export async function answerDecision(id, answer_md, config = loadConfig()) {
|
|
85
88
|
return request(config, "POST", `/api/w/${config.slug}/decisions/${encodeURIComponent(id)}/answer`, { answer_md });
|
|
86
89
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { approveEpic, answerDecision, cancelTicket, mergeTicket, createEpic, deleteAgent, deleteEpic, getStatus, getRoadmap, getWorkspace, listAgents, listEpics, listHostEnv, listMessages, listWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage, queueTicket, removeWorkspaceEnv, removeHostEnv, setPaused, setWorkspaceEnv, setHostEnv, showTicket, updateAgent, updateEpic, updateCaps, } from "./api.js";
|
|
4
|
+
import { approveEpic, answerDecision, cancelTicket, cancelRun, mergeTicket, createEpic, deleteAgent, deleteEpic, getStatus, getRoadmap, getWorkspace, listAgents, listEpics, listHostEnv, listMessages, listWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage, queueTicket, removeWorkspaceEnv, removeHostEnv, setPaused, setWorkspaceEnv, setHostEnv, showTicket, updateAgent, updateEpic, updateCaps, } from "./api.js";
|
|
5
5
|
import { formatDrainStatus, hostRoll, initHost, parseHostFlags, parseHostRollFlags } from "./host.js";
|
|
6
6
|
import { login, parseLoginFlags } from "./login.js";
|
|
7
7
|
import { loadConfig } from "./config.js";
|
|
@@ -89,6 +89,25 @@ async function cmdStatus() {
|
|
|
89
89
|
console.log(`\n${c.bold("Chat replies")} median ${seconds}s (last day)`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
export function runAge(started, created, now = Date.now()) {
|
|
93
|
+
const seconds = Math.max(0, Math.floor((now - Date.parse(started ?? created)) / 1000));
|
|
94
|
+
return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
95
|
+
}
|
|
96
|
+
async function cmdRun(argv) {
|
|
97
|
+
if (argv[0] !== "cancel" || !argv[1] || argv.length !== 2)
|
|
98
|
+
fail("usage: hd run cancel ID");
|
|
99
|
+
const { run } = await cancelRun(argv[1]);
|
|
100
|
+
console.log(`${run.id} cancellation requested`);
|
|
101
|
+
}
|
|
102
|
+
async function cmdRuns(argv) {
|
|
103
|
+
if (argv.length)
|
|
104
|
+
fail("usage: hd runs");
|
|
105
|
+
const [{ live_runs: runs }, { agents }] = await Promise.all([getStatus(), listAgents()]);
|
|
106
|
+
const names = new Map(agents.map((agent) => [agent.id, agent.display_name]));
|
|
107
|
+
console.log(table(["ID", "AGE", "KIND", "AGENT"], runs.map((run) => [
|
|
108
|
+
run.id, runAge(run.started_at, run.created_at), run.kind, names.get(run.agent_id ?? "") ?? run.provider,
|
|
109
|
+
])));
|
|
110
|
+
}
|
|
92
111
|
async function cmdTicket(argv, deps = {}) {
|
|
93
112
|
const [action, ...rest] = argv;
|
|
94
113
|
if (action === "list") {
|
|
@@ -445,8 +464,10 @@ async function cmdAgents(argv, deps = {}) {
|
|
|
445
464
|
};
|
|
446
465
|
if (!Object.keys(fields).length)
|
|
447
466
|
fail(agentUsage);
|
|
448
|
-
const { agent } = await updateAgent(current.id, fields);
|
|
467
|
+
const { agent, cancelled_runs = 0 } = await updateAgent(current.id, fields);
|
|
449
468
|
console.log(`${agent.display_name} ${agent.role} ${agent.provider} ${agent.model} ${agent.enabled ? "on" : "off"}`);
|
|
469
|
+
if (cancelled_runs)
|
|
470
|
+
console.log(`cancellation requested for ${cancelled_runs} live run${cancelled_runs === 1 ? "" : "s"}`);
|
|
450
471
|
}
|
|
451
472
|
export function mailerCapsRow(auth) {
|
|
452
473
|
if (auth === "smtp")
|
|
@@ -622,6 +643,14 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
622
643
|
await cmdTicket(rest, deps);
|
|
623
644
|
return;
|
|
624
645
|
}
|
|
646
|
+
if (cmd === "run") {
|
|
647
|
+
await cmdRun(rest);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (cmd === "runs") {
|
|
651
|
+
await cmdRuns(rest);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
625
654
|
if (cmd === "epic") {
|
|
626
655
|
await cmdEpic(rest);
|
|
627
656
|
return;
|
|
@@ -658,7 +687,7 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
658
687
|
await cmdWorkspace(rest, deps);
|
|
659
688
|
return;
|
|
660
689
|
}
|
|
661
|
-
if (cmd === "agents") {
|
|
690
|
+
if (cmd === "agents" || cmd === "agent") {
|
|
662
691
|
await cmdAgents(rest, deps);
|
|
663
692
|
return;
|
|
664
693
|
}
|
package/dist/out.js
CHANGED
|
@@ -65,6 +65,7 @@ export function usage() {
|
|
|
65
65
|
c.bold("Usage"),
|
|
66
66
|
` ${c.blue("hd status")} workspace overview`,
|
|
67
67
|
` ${c.blue("hd ticket list | show KEY [--json] | new [PATH] | queue | cancel | merge")} ticket operations`,
|
|
68
|
+
` ${c.blue("hd runs | hd run cancel ID")} inspect or cancel live runs`,
|
|
68
69
|
` ${c.blue("hd epic new PATH | list | approve | rm")} epic operations`,
|
|
69
70
|
` ${c.blue("hd roadmap [--json]")} ordered workspace roadmap`,
|
|
70
71
|
` ${c.blue("hd plan")} use /architect in the TUI`,
|
package/dist/tui/App.js
CHANGED
|
@@ -25,7 +25,7 @@ import { alertOnce } from "./alert.js";
|
|
|
25
25
|
import { bubbleRows } from "./height.js";
|
|
26
26
|
import { planLayout, splitPanels } from "./layout.js";
|
|
27
27
|
import { parseLine } from "./parse.js";
|
|
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";
|
|
28
|
+
import { configuredSlugs, acknowledgeInbox, approveEpic, cancelTicket, cancelRun, 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
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";
|
|
@@ -315,16 +315,20 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
|
|
|
315
315
|
}
|
|
316
316
|
setBusy(true);
|
|
317
317
|
try {
|
|
318
|
+
let message = null;
|
|
318
319
|
if (edit.value.target === "cap") {
|
|
319
320
|
await updateProviderCap(config, edit.value.provider, edit.value.cap);
|
|
320
321
|
}
|
|
321
322
|
else if (edit.value.target === "agent") {
|
|
322
|
-
await updateAgent(config, edit.value.id, edit.value.fields);
|
|
323
|
+
const result = await updateAgent(config, edit.value.id, edit.value.fields);
|
|
324
|
+
const count = result.cancelled_runs ?? 0;
|
|
325
|
+
if (count)
|
|
326
|
+
message = `Saved. Cancellation requested for ${count} live run${count === 1 ? "" : "s"}.`;
|
|
323
327
|
}
|
|
324
328
|
else {
|
|
325
329
|
await updateWorkspace(config, edit.value.fields);
|
|
326
330
|
}
|
|
327
|
-
setNotice(
|
|
331
|
+
setNotice(message);
|
|
328
332
|
await refresh();
|
|
329
333
|
}
|
|
330
334
|
catch (error) {
|
|
@@ -767,6 +771,20 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
|
|
|
767
771
|
setBusy(false);
|
|
768
772
|
}
|
|
769
773
|
return;
|
|
774
|
+
case "cancel-run":
|
|
775
|
+
setBusy(true);
|
|
776
|
+
try {
|
|
777
|
+
await cancelRun(config, action.id);
|
|
778
|
+
say("system", `Cancellation requested for run ${action.id}.`);
|
|
779
|
+
await refresh();
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
setNotice(error instanceof Error ? error.message : String(error));
|
|
783
|
+
}
|
|
784
|
+
finally {
|
|
785
|
+
setBusy(false);
|
|
786
|
+
}
|
|
787
|
+
return;
|
|
770
788
|
case "merge":
|
|
771
789
|
setBusy(true);
|
|
772
790
|
try {
|
package/dist/tui/Help.js
CHANGED
|
@@ -10,7 +10,7 @@ export const COMMANDS = [
|
|
|
10
10
|
{ name: "/inbox", args: "[more]", help: "unread, earlier, and numbered decisions; Enter answers" },
|
|
11
11
|
{ name: "/ticket", args: "HD-12 | new [PATH.md]", help: "open a full ticket view or create one" },
|
|
12
12
|
{ name: "/queue", args: "HD-12", help: "queue a complete ticket now" },
|
|
13
|
-
{ name: "/cancel", args: "HD-12", help: "cancel a ticket" },
|
|
13
|
+
{ name: "/cancel", args: "HD-12 | RUN-ID", help: "cancel a ticket or run" },
|
|
14
14
|
{ name: "/merge", args: "HD-12", help: "approve a reviewed PR over the reviewer's objections" },
|
|
15
15
|
{ name: "/msg", args: "HD-12 TEXT", help: "message a ticket's builder" },
|
|
16
16
|
{ name: "/attach", args: "PATH [HD-12]", help: "attach a file to the open or named ticket" },
|
package/dist/tui/chat-view.js
CHANGED
|
@@ -96,7 +96,7 @@ export function followShouldStop(run, reply, tagged = false) {
|
|
|
96
96
|
return true;
|
|
97
97
|
if (!run)
|
|
98
98
|
return false;
|
|
99
|
-
if (["failed", "killed"].includes(run.status))
|
|
99
|
+
if (["failed", "killed", "cancelled"].includes(run.status))
|
|
100
100
|
return true;
|
|
101
101
|
// The role's other runs may finish while ours is still queued; only the run
|
|
102
102
|
// started for this message is definitive when it ends without a reply.
|
package/dist/tui/chat-wait.js
CHANGED
|
@@ -28,7 +28,7 @@ export function settleChatReply(reply, run) {
|
|
|
28
28
|
if (reply) {
|
|
29
29
|
return { body: reply, pending: false, steps: [], done: true, replyMs: run?.reply_ms ?? null };
|
|
30
30
|
}
|
|
31
|
-
if (run && ["failed", "killed"].includes(run.status ?? "")) {
|
|
31
|
+
if (run && ["failed", "killed", "cancelled"].includes(run.status ?? "")) {
|
|
32
32
|
return { body: failedChatNote(run.summary), pending: false, steps: [], done: true, replyMs: run.reply_ms ?? null };
|
|
33
33
|
}
|
|
34
34
|
if (run && run.status && !["queued", "running"].includes(run.status)) {
|
package/dist/tui/data.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { approveEpic as approveEpicNow, answerDecision, cancelTicket as cancelTicketNow, createAgent as postAgent, createEpic as postEpic, deleteEpic as removeEpic, getStatus, getRoadmap, getRun, getWorkspace, listAgents, listFeed, listMessages, markMessagesDelivered, listWorkspaceEnv as getWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage as sendMessage, mergeTicket as mergeTicketNow, queueTicket as queueTicketNow, setPaused as setPausedNow, showTicket, updateAgent as patchAgent, updateCaps, updateWorkspace as patchWorkspace, deleteAgent as removeAgent, } from "../api.js";
|
|
1
|
+
import { approveEpic as approveEpicNow, answerDecision, cancelTicket as cancelTicketNow, cancelRun as cancelRunNow, createAgent as postAgent, createEpic as postEpic, deleteEpic as removeEpic, getStatus, getRoadmap, getRun, getWorkspace, listAgents, listFeed, listMessages, markMessagesDelivered, listWorkspaceEnv as getWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage as sendMessage, mergeTicket as mergeTicketNow, queueTicket as queueTicketNow, setPaused as setPausedNow, showTicket, updateAgent as patchAgent, updateCaps, updateWorkspace as patchWorkspace, deleteAgent as removeAgent, } from "../api.js";
|
|
2
2
|
import { loadConfig, switchWorkspace as selectWorkspace, } from "../config.js";
|
|
3
3
|
import { readEpicSpec } from "../epics.js";
|
|
4
4
|
import { EARLIER_PAGE } from "./inbox.js";
|
|
@@ -70,6 +70,7 @@ export async function loadSnapshot(config = loadConfig(), options = {}) {
|
|
|
70
70
|
chat: Number(workspaceData.workspace.settings.max_turns?.chat ?? 8),
|
|
71
71
|
},
|
|
72
72
|
max_attempts: Number(workspaceData.workspace.settings.max_attempts ?? 3),
|
|
73
|
+
chat_timeout_ms: Number(workspaceData.workspace.settings.chat_timeout_ms ?? 600_000),
|
|
73
74
|
},
|
|
74
75
|
board: {
|
|
75
76
|
tickets,
|
|
@@ -129,6 +130,9 @@ export async function postAgentMessage(role, body, config, attachmentIds = []) {
|
|
|
129
130
|
attachment_ids: attachmentIds }, config);
|
|
130
131
|
return message;
|
|
131
132
|
}
|
|
133
|
+
export async function cancelRun(config, id) {
|
|
134
|
+
await cancelRunNow(id, config);
|
|
135
|
+
}
|
|
132
136
|
export async function loadChatMessages(config, role) {
|
|
133
137
|
const { messages } = await listMessages({
|
|
134
138
|
unticketed: true,
|
package/dist/tui/parse.js
CHANGED
|
@@ -79,7 +79,9 @@ export function parseLine(raw) {
|
|
|
79
79
|
? { kind: "queue", key: argument.toUpperCase() }
|
|
80
80
|
: { kind: "unknown", command: "queue needs a key" };
|
|
81
81
|
case "cancel":
|
|
82
|
-
return argument ?
|
|
82
|
+
return argument ? /^HD-\d+$/i.test(argument)
|
|
83
|
+
? { kind: "cancel", key: argument.toUpperCase() }
|
|
84
|
+
: { kind: "cancel-run", id: argument }
|
|
83
85
|
: { kind: "unknown", command: "cancel needs a key" };
|
|
84
86
|
case "merge":
|
|
85
87
|
return argument ? { kind: "merge", key: argument.toUpperCase() }
|
|
@@ -12,6 +12,7 @@ export function settingsRows(workspace, agents) {
|
|
|
12
12
|
value: String(workspace.max_turns[kind]), hint: "integer >= 1",
|
|
13
13
|
})),
|
|
14
14
|
{ key: "w:max_attempts", kind: "number", label: "max attempts", value: String(workspace.max_attempts) },
|
|
15
|
+
{ key: "w:chat_timeout_ms", kind: "number", label: "chat timeout ms", value: String(workspace.chat_timeout_ms ?? 600_000) },
|
|
15
16
|
];
|
|
16
17
|
for (const provider of providers) {
|
|
17
18
|
rows.push({
|
|
@@ -81,6 +82,12 @@ export function editFor(row, raw) {
|
|
|
81
82
|
return { ok: false, error: "max_attempts must be an integer >= 1." };
|
|
82
83
|
return { ok: true, value: { target: "workspace", fields: { max_attempts: number } } };
|
|
83
84
|
}
|
|
85
|
+
if (id === "chat_timeout_ms") {
|
|
86
|
+
const number = Number(value);
|
|
87
|
+
if (!Number.isInteger(number) || number < 1_000)
|
|
88
|
+
return { ok: false, error: "chat_timeout_ms must be an integer >= 1000." };
|
|
89
|
+
return { ok: true, value: { target: "workspace", fields: { chat_timeout_ms: number } } };
|
|
90
|
+
}
|
|
84
91
|
if (id === "auto_merge")
|
|
85
92
|
return { ok: true, value: { target: "workspace", fields: { auto_merge: value === "yes" } } };
|
|
86
93
|
if (!value)
|
package/dist/tui/stream.js
CHANGED
|
@@ -39,7 +39,7 @@ export function toStreamLines(events, runs, raw = false) {
|
|
|
39
39
|
const target = run.ticket ? ` on ${run.ticket}` : "";
|
|
40
40
|
lines.push({ id: `${run.runId}:end`, sourceIds: [`${run.runId}:end`], runId: run.runId,
|
|
41
41
|
agent: run.agent, at: run.endedAt ?? "", seq: Number.MAX_SAFE_INTEGER,
|
|
42
|
-
kind:
|
|
42
|
+
kind: ["failed", "killed", "cancelled"].includes(run.status) ? "error" : "status",
|
|
43
43
|
title: `${run.agent} finished ${run.kind}${target}: ${run.summary}` });
|
|
44
44
|
}
|
|
45
45
|
return collapse(lines.sort((a, b) => a.at.localeCompare(b.at) || a.seq - b.seq), raw);
|
|
@@ -150,6 +150,12 @@ export async function workspaceSet(argv) {
|
|
|
150
150
|
throw new Error("--max-attempts must be an integer >= 1");
|
|
151
151
|
fields.max_attempts = value;
|
|
152
152
|
}
|
|
153
|
+
if (opts["chat-timeout-ms"]) {
|
|
154
|
+
const value = Number(opts["chat-timeout-ms"]);
|
|
155
|
+
if (!Number.isInteger(value) || value < 1_000)
|
|
156
|
+
throw new Error("--chat-timeout-ms must be an integer >= 1000");
|
|
157
|
+
fields.chat_timeout_ms = value;
|
|
158
|
+
}
|
|
153
159
|
if (!Object.keys(fields).length)
|
|
154
160
|
throw new Error(WORKSPACE_USAGE);
|
|
155
161
|
return (await updateWorkspace(fields)).workspace;
|