@higherdev/cli 0.13.0 → 0.14.3
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/README.md +5 -1
- package/dist/api.js +16 -0
- package/dist/index.js +37 -2
- package/dist/out.js +2 -1
- package/dist/tui/App.js +29 -1
- package/dist/tui/Dashboard.js +2 -2
- package/dist/tui/Help.js +1 -0
- package/dist/tui/Panels.js +18 -5
- package/dist/tui/agent-rows.js +28 -10
- package/dist/tui/data.js +11 -2
- package/dist/tui/parse.js +2 -0
- package/dist/workspace-commands.js +14 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
35
35
|
| `hd epic rm ID` | Remove a draft epic |
|
|
36
36
|
| `hd plan` | Point to the TUI `/architect` conversation |
|
|
37
37
|
| `hd workspace ls` | List every workspace available to the configured key |
|
|
38
|
+
| `hd workspace use SLUG` | Switch the current workspace |
|
|
38
39
|
| `hd workspace new --name NAME --repo OWNER/NAME [options]` | Preflight GitHub, wire the runner, and create a paused workspace |
|
|
39
40
|
| `hd workspace set [options]` | Update settings; max turns uses `--max-turns KIND=N[,KIND=N...]` |
|
|
40
41
|
| `hd workspace rotate-key` | Rotate the shared API key and save it locally |
|
|
@@ -43,6 +44,9 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
43
44
|
| `hd agents rm ROLE\|ID` | Remove an unambiguous agent |
|
|
44
45
|
| `hd agents set ROLE\|ID [options]` | Rename, configure, enable, or disable an agent |
|
|
45
46
|
| `hd caps [set PROVIDER N]` | Show or update provider concurrency caps |
|
|
47
|
+
| `hd env ls` | List workspace environment variable names |
|
|
48
|
+
| `hd env set NAME=VALUE [NAME=VALUE...]` | Set workspace environment variables |
|
|
49
|
+
| `hd env rm NAME` | Remove a workspace environment variable |
|
|
46
50
|
| `hd logs KEY [-f]` | Show or follow run events |
|
|
47
51
|
| `hd msg KEY "message" [--interrupt]` | Message a builder |
|
|
48
52
|
| `hd decide` | List open decisions |
|
|
@@ -64,7 +68,7 @@ the repository is missing, approve private creation interactively, use `--create
|
|
|
64
68
|
`--no-create` to fail. Fully flagged calls remain non-interactive for scripts and Mel.
|
|
65
69
|
|
|
66
70
|
Inside the TUI, use `/board`, `/inbox`, `/ticket`, `/queue`, `/cancel`, `/epic new`,
|
|
67
|
-
`/epic approve`, `/epics`, `/architect` (or `/plan`), `/decide`, `/agents add`, `/agents rm`, `/settings`, `/workspace`,
|
|
71
|
+
`/epic approve`, `/epics`, `/architect` (or `/plan`), `/decide`, `/agents add`, `/agents rm`, `/env`, `/settings`, `/workspace`,
|
|
68
72
|
`/workspace new`, `/workspace set`, `/workspace rotate-key`, `/feed`,
|
|
69
73
|
`/on`, `/off`, `/orchestrator`, `/refresh`, `/help`, or `/exit`. The display refreshes from
|
|
70
74
|
the HDX API every five seconds.
|
package/dist/api.js
CHANGED
|
@@ -105,6 +105,15 @@ export async function updateAgent(id, fields, config = loadConfig()) {
|
|
|
105
105
|
export async function deleteAgent(id, config = loadConfig()) {
|
|
106
106
|
return request(config, "DELETE", `/api/w/${config.slug}/agents`, { id });
|
|
107
107
|
}
|
|
108
|
+
export async function listWorkspaceEnv(config = loadConfig()) {
|
|
109
|
+
return request(config, "GET", `/api/w/${config.slug}/env`);
|
|
110
|
+
}
|
|
111
|
+
export async function setWorkspaceEnv(name, value, config = loadConfig()) {
|
|
112
|
+
return request(config, "PUT", `/api/w/${config.slug}/env`, { name, value });
|
|
113
|
+
}
|
|
114
|
+
export async function removeWorkspaceEnv(name, config = loadConfig()) {
|
|
115
|
+
return request(config, "DELETE", `/api/w/${config.slug}/env`, { name });
|
|
116
|
+
}
|
|
108
117
|
export async function listEpics(config = loadConfig()) {
|
|
109
118
|
return request(config, "GET", `/api/w/${config.slug}/epics`);
|
|
110
119
|
}
|
|
@@ -125,9 +134,16 @@ export async function listMessages(options = {}, config = loadConfig()) {
|
|
|
125
134
|
query.set("since", options.since);
|
|
126
135
|
if (options.limit)
|
|
127
136
|
query.set("limit", String(options.limit));
|
|
137
|
+
if (options.toRoles?.length)
|
|
138
|
+
query.set("to_role", options.toRoles.join(","));
|
|
139
|
+
if (options.undelivered)
|
|
140
|
+
query.set("undelivered", "true");
|
|
128
141
|
const suffix = query.size ? `?${query.toString()}` : "";
|
|
129
142
|
return request(config, "GET", `/api/w/${config.slug}/messages${suffix}`);
|
|
130
143
|
}
|
|
144
|
+
export async function markMessagesDelivered(ids, config = loadConfig()) {
|
|
145
|
+
return request(config, "PATCH", `/api/w/${config.slug}/messages`, { ids });
|
|
146
|
+
}
|
|
131
147
|
export async function updateCaps(provider_caps, config = loadConfig()) {
|
|
132
148
|
return request(config, "PATCH", `/api/w/${config.slug}/caps`, { provider_caps });
|
|
133
149
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
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, createAgent, createEpic, createTicket, deleteAgent, deleteEpic, getStatus, listAgents, listEpics, listTicketEvents, listTickets, listWorkspaces, postMessage, queueTicket, setPaused, showTicket, updateAgent, updateCaps, } from "./api.js";
|
|
4
|
+
import { approveEpic, answerDecision, cancelTicket, createAgent, createEpic, createTicket, deleteAgent, deleteEpic, getStatus, listAgents, listEpics, listWorkspaceEnv, listTicketEvents, listTickets, listWorkspaces, postMessage, queueTicket, removeWorkspaceEnv, setPaused, setWorkspaceEnv, showTicket, updateAgent, updateCaps, } from "./api.js";
|
|
5
5
|
import { initHost, parseHostFlags } from "./host.js";
|
|
6
6
|
import { login, parseLoginFlags } from "./login.js";
|
|
7
7
|
import { loadConfig } from "./config.js";
|
|
8
8
|
import { epicProgressRows, readEpicSpec } from "./epics.js";
|
|
9
9
|
import { banner, c, statusChip, table, truncate, usage } from "./out.js";
|
|
10
|
-
import { WORKSPACE_USAGE, workspaceNew, workspaceRotateKey, workspaceSet } from "./workspace-commands.js";
|
|
10
|
+
import { WORKSPACE_USAGE, workspaceNew, workspaceRotateKey, workspaceSet, workspaceUse } from "./workspace-commands.js";
|
|
11
11
|
function fail(message) {
|
|
12
12
|
console.error(message);
|
|
13
13
|
process.exit(1);
|
|
@@ -252,6 +252,11 @@ async function cmdWorkspace(argv, deps = {}) {
|
|
|
252
252
|
])));
|
|
253
253
|
return;
|
|
254
254
|
}
|
|
255
|
+
if (action === "use") {
|
|
256
|
+
const workspace = await workspaceUse(rest);
|
|
257
|
+
console.log(`${workspace.slug} ${workspace.name} ${workspace.repo}`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
255
260
|
if (action === "rotate-key") {
|
|
256
261
|
if (rest.length)
|
|
257
262
|
fail(WORKSPACE_USAGE);
|
|
@@ -344,6 +349,32 @@ async function cmdCaps(argv) {
|
|
|
344
349
|
const result = await updateCaps({ [provider]: cap });
|
|
345
350
|
console.log(`${provider} ${result.provider_caps[provider]}`);
|
|
346
351
|
}
|
|
352
|
+
async function cmdEnv(argv) {
|
|
353
|
+
const [action, ...args] = argv;
|
|
354
|
+
if (action === "ls") {
|
|
355
|
+
const { env } = await listWorkspaceEnv();
|
|
356
|
+
if (!env.length)
|
|
357
|
+
return console.log(c.dim("No workspace environment variables."));
|
|
358
|
+
console.log(table(["NAME", "UPDATED"], env.map((row) => [row.name, row.updated_at])));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (action === "set" && args.length) {
|
|
362
|
+
for (const assignment of args) {
|
|
363
|
+
const at = assignment.indexOf("=");
|
|
364
|
+
if (at < 1)
|
|
365
|
+
fail("usage: hd env set NAME=VALUE [NAME=VALUE...]");
|
|
366
|
+
await setWorkspaceEnv(assignment.slice(0, at), assignment.slice(at + 1));
|
|
367
|
+
}
|
|
368
|
+
console.log(`set ${args.length} variable${args.length === 1 ? "" : "s"}`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (action === "rm" && args.length === 1) {
|
|
372
|
+
await removeWorkspaceEnv(args[0]);
|
|
373
|
+
console.log(`removed ${args[0]}`);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
fail("usage: hd env ls | set NAME=VALUE [NAME=VALUE...] | rm NAME");
|
|
377
|
+
}
|
|
347
378
|
export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
348
379
|
const [cmd, ...rest] = argv;
|
|
349
380
|
try {
|
|
@@ -405,6 +436,10 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
405
436
|
await cmdCaps(rest);
|
|
406
437
|
return;
|
|
407
438
|
}
|
|
439
|
+
if (cmd === "env") {
|
|
440
|
+
await cmdEnv(rest);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
408
443
|
if (cmd === "pause" || cmd === "off") {
|
|
409
444
|
await setPaused(true);
|
|
410
445
|
console.log(cmd === "off" ? "off" : "paused");
|
package/dist/out.js
CHANGED
|
@@ -67,9 +67,10 @@ export function usage() {
|
|
|
67
67
|
` ${c.blue("hd ticket list | show | new | queue | cancel")} ticket operations`,
|
|
68
68
|
` ${c.blue("hd epic new PATH | list | approve | rm")} epic operations`,
|
|
69
69
|
` ${c.blue("hd plan")} use /architect in the TUI`,
|
|
70
|
-
` ${c.blue("hd workspace ls | new | set | rotate-key")} workspace operations`,
|
|
70
|
+
` ${c.blue("hd workspace ls | use | new | set | rotate-key")} workspace operations`,
|
|
71
71
|
` ${c.blue("hd agents [add | rm | set]")} manage agents`,
|
|
72
72
|
` ${c.blue("hd caps [set PROVIDER N]")} provider concurrency`,
|
|
73
|
+
` ${c.blue("hd env ls | set | rm")} workspace environment`,
|
|
73
74
|
` ${c.blue("hd logs KEY [-f]")} run events`,
|
|
74
75
|
` ${c.blue("hd msg KEY TEXT")} message a builder`,
|
|
75
76
|
` ${c.blue("hd decide [ID --answer TEXT]")} decisions`,
|
package/dist/tui/App.js
CHANGED
|
@@ -18,7 +18,7 @@ import { alertOnce } from "./alert.js";
|
|
|
18
18
|
import { bubbleRows } from "./height.js";
|
|
19
19
|
import { planLayout, splitPanels } from "./layout.js";
|
|
20
20
|
import { parseLine } from "./parse.js";
|
|
21
|
-
import { configuredSlugs, approveEpic, cancelTicket, createAgent, createEpicFromFile, decisionOptions, deleteAgent, loadLiveEvents, loadTicketDetail, pollSnapshot, postAgentMessage, queueTicket, resolveDecision, setWorkspacePaused, switchWorkspace, updateAgent, updateProviderCap, updateWorkspace, } from "./data.js";
|
|
21
|
+
import { configuredSlugs, acknowledgeInbox, approveEpic, cancelTicket, createAgent, createEpicFromFile, decisionOptions, deleteAgent, loadLiveEvents, listWorkspaceEnv, loadTicketDetail, pollSnapshot, postAgentMessage, queueTicket, resolveDecision, setWorkspacePaused, switchWorkspace, updateAgent, updateProviderCap, updateWorkspace, } from "./data.js";
|
|
22
22
|
import { editFor, editableKeys, nextValue, seedFor, settingsRows } from "./settings-model.js";
|
|
23
23
|
import { appendLines, runLabels, toStreamLines } from "./stream.js";
|
|
24
24
|
import { UI } from "./theme.js";
|
|
@@ -57,6 +57,7 @@ export function App({ initial }) {
|
|
|
57
57
|
const history = useRef([]);
|
|
58
58
|
const historyAt = useRef(-1);
|
|
59
59
|
const refreshRef = useRef(null);
|
|
60
|
+
const acknowledgedMessages = useRef(new Set());
|
|
60
61
|
const loads = useRef(new WorkspaceLoads(initial.workspace.id));
|
|
61
62
|
editingRef.current = editing;
|
|
62
63
|
useEffect(() => {
|
|
@@ -143,6 +144,19 @@ export function App({ initial }) {
|
|
|
143
144
|
useEffect(() => {
|
|
144
145
|
setInboxFocus((current) => Math.max(0, Math.min(inbox.length - 1, current)));
|
|
145
146
|
}, [inbox.length]);
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (view !== "inbox")
|
|
149
|
+
return;
|
|
150
|
+
const ids = (board.messages ?? []).map((message) => message.id)
|
|
151
|
+
.filter((id) => !acknowledgedMessages.current.has(id));
|
|
152
|
+
if (!ids.length)
|
|
153
|
+
return;
|
|
154
|
+
ids.forEach((id) => acknowledgedMessages.current.add(id));
|
|
155
|
+
void acknowledgeInbox(config, ids).catch((error) => {
|
|
156
|
+
ids.forEach((id) => acknowledgedMessages.current.delete(id));
|
|
157
|
+
setNotice(error instanceof Error ? error.message : String(error));
|
|
158
|
+
});
|
|
159
|
+
}, [view, board.messages, config]);
|
|
146
160
|
const refresh = useCallback(async () => {
|
|
147
161
|
await refreshRef.current?.();
|
|
148
162
|
}, []);
|
|
@@ -186,6 +200,7 @@ export function App({ initial }) {
|
|
|
186
200
|
setBoard(snapshot.board);
|
|
187
201
|
setFeed(snapshot.feed);
|
|
188
202
|
setStream([]);
|
|
203
|
+
acknowledgedMessages.current.clear();
|
|
189
204
|
setCursor(null);
|
|
190
205
|
selectedRef.current = null;
|
|
191
206
|
setView("home");
|
|
@@ -472,6 +487,19 @@ export function App({ initial }) {
|
|
|
472
487
|
}
|
|
473
488
|
return;
|
|
474
489
|
}
|
|
490
|
+
case "env":
|
|
491
|
+
setBusy(true);
|
|
492
|
+
try {
|
|
493
|
+
const { env } = await listWorkspaceEnv(config);
|
|
494
|
+
say("system", env.length ? env.map((row) => row.name).join("\n") : "No workspace environment variables.");
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
setNotice(error instanceof Error ? error.message : String(error));
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
setBusy(false);
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
475
503
|
case "decide": {
|
|
476
504
|
const decision = board.decisions[0];
|
|
477
505
|
if (!decision) {
|
package/dist/tui/Dashboard.js
CHANGED
|
@@ -117,10 +117,10 @@ export function AgentsColumn({ board, width, rows, }) {
|
|
|
117
117
|
]
|
|
118
118
|
: []),
|
|
119
119
|
...shown.map((row) => {
|
|
120
|
-
const tone = row.run ? "blue" : row.state === "offline" ? "warning" : "muted";
|
|
120
|
+
const tone = row.run ? "blue" : row.state === "offline" || row.state === "limited" ? "warning" : "muted";
|
|
121
121
|
return (_jsxs(Box, { flexWrap: "nowrap", children: [_jsxs(Text, { color: inkColor(tone), children: [DOT, " "] }), _jsxs(Text, { color: UI.text, wrap: "truncate", children: [row.name, _jsx(Text, { color: UI.dim, children: row.run
|
|
122
122
|
? ` · ${row.ticket?.key ?? row.run.kind} ${elapsed(row.run.started_at ?? row.run.created_at)}`
|
|
123
|
-
: ` · ${row.state}` })] })] }, row.key));
|
|
123
|
+
: ` · ${row.limitedUntil ? `limited until ${row.limitedUntil}` : row.state}` })] })] }, row.key));
|
|
124
124
|
}),
|
|
125
125
|
_jsx(More, { count: displayRows.length - shown.length }, "more"),
|
|
126
126
|
] }));
|
package/dist/tui/Help.js
CHANGED
|
@@ -16,6 +16,7 @@ export const COMMANDS = [
|
|
|
16
16
|
{ name: "/plan", help: "alias for /architect" },
|
|
17
17
|
{ name: "/decide", args: "2 | text", help: "answer the decision on screen" },
|
|
18
18
|
{ name: "/agents", args: "[add ... | rm ROLE|ID]", help: "view or manage agents" },
|
|
19
|
+
{ name: "/env", help: "list workspace environment variable names" },
|
|
19
20
|
{ name: "/settings", help: "change provider caps and agent settings" },
|
|
20
21
|
{ name: "/workspace", args: "[slug | new | set | rotate-key]", help: "list, switch, create, or configure" },
|
|
21
22
|
{ name: "/on", help: "turn on the current workspace" },
|
package/dist/tui/Panels.js
CHANGED
|
@@ -25,8 +25,9 @@ export function AgentsPanel({ board, width = 80, rows = 12, }) {
|
|
|
25
25
|
]
|
|
26
26
|
: []),
|
|
27
27
|
...shown.map((row) => {
|
|
28
|
-
const tone = row.run ? "blue" : row.state === "offline" ? "warning" : "muted";
|
|
29
|
-
|
|
28
|
+
const tone = row.run ? "blue" : row.state === "offline" || row.state === "limited" ? "warning" : "muted";
|
|
29
|
+
const state = row.limitedUntil ? `limited until ${row.limitedUntil}` : row.state;
|
|
30
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: inkColor(tone), children: [DOT, " "] }), _jsx(Text, { color: UI.text, children: pad(truncate(row.name, 13), 14) }), _jsx(Text, { color: UI.dim, children: pad(row.agent?.role ?? row.run?.kind ?? "", 13) }), _jsx(Text, { color: UI.dim, children: pad(truncate(row.agent?.model ?? "", 23), 24) }), _jsx(Text, { color: UI.text, children: pad(state, row.limitedUntil ? 22 : 9) }), _jsxs(Text, { color: UI.dim, children: [row.ticket ? `${row.ticket.key} ` : "", row.run ? elapsed(row.run.started_at ?? row.run.created_at) : ""] })] }, row.key));
|
|
30
31
|
}),
|
|
31
32
|
_jsx(More, { count: displayRows.length - shown.length }, "more"),
|
|
32
33
|
] }));
|
|
@@ -85,14 +86,16 @@ export function FeedPanel({ entries, width = 80, rows = 12, }) {
|
|
|
85
86
|
*/
|
|
86
87
|
export function InboxPanel({ board, width = 80, rows = 12, focus = 0, }) {
|
|
87
88
|
const entries = inboxEntries(board, width);
|
|
89
|
+
const messages = board.messages ?? [];
|
|
88
90
|
const inner = Math.max(0, rows - 1);
|
|
89
91
|
const window = scrollWindow(entries.length, inner, focus);
|
|
90
92
|
const hiddenAbove = window.start;
|
|
91
93
|
const hiddenBelow = entries.length - window.end;
|
|
92
|
-
const note = `${board.decisions.length}
|
|
94
|
+
const note = `${board.decisions.length}d · ${messages.length}m`
|
|
95
|
+
+ `${hiddenAbove ? ` ${hiddenAbove}↑` : ""}${hiddenBelow ? ` ${hiddenBelow}↓` : ""}`;
|
|
93
96
|
return (_jsx(Panel, { width: width, rows: rows, children: [
|
|
94
|
-
_jsx(Heading, { text: "
|
|
95
|
-
...(board.decisions.length === 0
|
|
97
|
+
_jsx(Heading, { text: "Inbox", note: note }, "h"),
|
|
98
|
+
...(board.decisions.length === 0 && messages.length === 0
|
|
96
99
|
? [
|
|
97
100
|
_jsx(Text, { color: UI.dim, children: "Nothing waiting on you." }, "empty"),
|
|
98
101
|
]
|
|
@@ -115,6 +118,16 @@ export function inboxEntries(board, width) {
|
|
|
115
118
|
entries.push({ key: `${decision.id}:body:${line}`, kind: "body", text });
|
|
116
119
|
});
|
|
117
120
|
});
|
|
121
|
+
(board.messages ?? []).forEach((message) => {
|
|
122
|
+
entries.push({
|
|
123
|
+
key: `${message.id}:header`,
|
|
124
|
+
kind: "header",
|
|
125
|
+
text: `Message from ${message.from_name ?? message.from_role}`,
|
|
126
|
+
});
|
|
127
|
+
wrapLines(message.body_md.trim(), bodyWidth).forEach((text, line) => {
|
|
128
|
+
entries.push({ key: `${message.id}:body:${line}`, kind: "body", text });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
118
131
|
return entries;
|
|
119
132
|
}
|
|
120
133
|
function wrapLines(text, width) {
|
package/dist/tui/agent-rows.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
const LIMITED_UNTIL = /^(\w+) limited until (\d{1,2}:\d{2})$/;
|
|
2
|
+
export function limitedUntilByProvider(tickets) {
|
|
3
|
+
const limited = new Map();
|
|
4
|
+
for (const ticket of tickets) {
|
|
5
|
+
const match = ticket.stuck_reason?.match(LIMITED_UNTIL);
|
|
6
|
+
if (match)
|
|
7
|
+
limited.set(match[1], match[2]);
|
|
8
|
+
}
|
|
9
|
+
return limited;
|
|
10
|
+
}
|
|
1
11
|
const roleForKind = {
|
|
2
12
|
architect: "architect",
|
|
3
13
|
build: "builder",
|
|
@@ -38,20 +48,28 @@ export function agentDisplayRows(board) {
|
|
|
38
48
|
run,
|
|
39
49
|
ticket,
|
|
40
50
|
state: run.status,
|
|
51
|
+
limitedUntil: null,
|
|
41
52
|
};
|
|
42
53
|
});
|
|
54
|
+
const limited = limitedUntilByProvider(board.tickets);
|
|
43
55
|
const idle = board.agents
|
|
44
56
|
.filter((agent) => agent.enabled && !activeAgents.has(agent.id))
|
|
45
57
|
.sort((left, right) => left.display_name.localeCompare(right.display_name))
|
|
46
|
-
.map((agent) =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
:
|
|
55
|
-
|
|
58
|
+
.map((agent) => {
|
|
59
|
+
const until = limited.get(agent.provider);
|
|
60
|
+
return {
|
|
61
|
+
key: `idle:${agent.id}`,
|
|
62
|
+
name: agent.display_name,
|
|
63
|
+
agent,
|
|
64
|
+
run: null,
|
|
65
|
+
ticket: null,
|
|
66
|
+
state: until
|
|
67
|
+
? "limited"
|
|
68
|
+
: board.availability.some((row) => row.provider === agent.provider && !row.available)
|
|
69
|
+
? "offline"
|
|
70
|
+
: "idle",
|
|
71
|
+
limitedUntil: until ?? null,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
56
74
|
return [...live, ...idle];
|
|
57
75
|
}
|
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, getStatus, getWorkspace, listAgents, listEpics, listFeed, listMessages, listTicketEvents, listTickets, listWorkspaces, postMessage as sendMessage, 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, createAgent as postAgent, createEpic as postEpic, getStatus, getWorkspace, listAgents, listEpics, listFeed, listMessages, markMessagesDelivered, listWorkspaceEnv as getWorkspaceEnv, listTicketEvents, listTickets, listWorkspaces, postMessage as sendMessage, 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
|
export const POLL_MS = 5_000;
|
|
@@ -22,13 +22,14 @@ export async function configuredSlugs(config = loadConfig()) {
|
|
|
22
22
|
return (await listWorkspaces(config)).map((workspace) => workspace.slug).sort();
|
|
23
23
|
}
|
|
24
24
|
export async function loadSnapshot(config = loadConfig()) {
|
|
25
|
-
const [status, workspaceData, ticketData, agentData, epicData, feedData] = await Promise.all([
|
|
25
|
+
const [status, workspaceData, ticketData, agentData, epicData, feedData, messageData] = await Promise.all([
|
|
26
26
|
getStatus(config),
|
|
27
27
|
getWorkspace(config),
|
|
28
28
|
listTickets(config),
|
|
29
29
|
listAgents(config),
|
|
30
30
|
listEpics(config),
|
|
31
31
|
listFeed(config),
|
|
32
|
+
listMessages({ toRoles: ["human", "all"], undelivered: true, limit: 500 }, config),
|
|
32
33
|
]);
|
|
33
34
|
const byId = new Map(ticketData.tickets.map((ticket) => [ticket.id, ticket]));
|
|
34
35
|
const agents = new Map(agentData.agents.map((agent) => [agent.id, agent]));
|
|
@@ -63,6 +64,7 @@ export async function loadSnapshot(config = loadConfig()) {
|
|
|
63
64
|
agents: agentData.agents,
|
|
64
65
|
epics: epicData.epics,
|
|
65
66
|
decisions: status.decisions,
|
|
67
|
+
messages: messageData.messages,
|
|
66
68
|
availability,
|
|
67
69
|
runs: status.live_runs,
|
|
68
70
|
},
|
|
@@ -127,6 +129,9 @@ export async function cancelTicket(config, key) {
|
|
|
127
129
|
export async function setWorkspacePaused(config, paused) {
|
|
128
130
|
return setPausedNow(paused, config);
|
|
129
131
|
}
|
|
132
|
+
export async function listWorkspaceEnv(config) {
|
|
133
|
+
return getWorkspaceEnv(config);
|
|
134
|
+
}
|
|
130
135
|
export async function createAgent(config, fields) {
|
|
131
136
|
return postAgent(fields, config);
|
|
132
137
|
}
|
|
@@ -148,6 +153,10 @@ export async function waitForReply(config, role, since, timeoutMs) {
|
|
|
148
153
|
export async function resolveDecision(config, id, answer) {
|
|
149
154
|
await answerDecision(id, answer, config);
|
|
150
155
|
}
|
|
156
|
+
export async function acknowledgeInbox(config, ids) {
|
|
157
|
+
if (ids.length)
|
|
158
|
+
await markMessagesDelivered(ids, config);
|
|
159
|
+
}
|
|
151
160
|
export async function updateAgent(config, id, fields) {
|
|
152
161
|
return patchAgent(id, fields, config);
|
|
153
162
|
}
|
package/dist/tui/parse.js
CHANGED
|
@@ -37,6 +37,8 @@ export function parseLine(raw) {
|
|
|
37
37
|
...(opts.effort ? { effort: opts.effort } : {}), ...(opts.name ? { name: opts.name } : {}) }
|
|
38
38
|
: { kind: "unknown", command: "agents needs add ROLE --provider P --model M or rm ROLE|ID" };
|
|
39
39
|
}
|
|
40
|
+
case "env":
|
|
41
|
+
return rest.length ? { kind: "unknown", command: "env takes no arguments" } : { kind: "env" };
|
|
40
42
|
case "help":
|
|
41
43
|
return { kind: "help" };
|
|
42
44
|
case "workspace":
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createWorkspace, rotateWorkspaceApiKey, updateWorkspace } from "./api.js";
|
|
2
|
-
import { loadConfig, writeHdConfig } from "./config.js";
|
|
1
|
+
import { createWorkspace, listWorkspaces, rotateWorkspaceApiKey, updateWorkspace } from "./api.js";
|
|
2
|
+
import { loadConfig, switchWorkspace, writeHdConfig } from "./config.js";
|
|
3
3
|
import { promptOnStdin } from "./prompt.js";
|
|
4
4
|
import { defaultGh, HDX_RUNNER_GH_USER, preflightWorkspace } from "./workspace-preflight.js";
|
|
5
|
-
export const WORKSPACE_USAGE = "usage: hd workspace ls | new --name NAME --repo OWNER/NAME [--create|--no-create] [flags] | set [flags] | rotate-key";
|
|
5
|
+
export const WORKSPACE_USAGE = "usage: hd workspace ls | use SLUG | new --name NAME --repo OWNER/NAME [--create|--no-create] [flags] | set [flags] | rotate-key";
|
|
6
6
|
function flags(argv) {
|
|
7
7
|
const opts = {};
|
|
8
8
|
const bools = new Set();
|
|
@@ -154,6 +154,17 @@ export async function workspaceSet(argv) {
|
|
|
154
154
|
throw new Error(WORKSPACE_USAGE);
|
|
155
155
|
return (await updateWorkspace(fields)).workspace;
|
|
156
156
|
}
|
|
157
|
+
export async function workspaceUse(argv) {
|
|
158
|
+
if (argv.length !== 1 || !argv[0])
|
|
159
|
+
throw new Error(WORKSPACE_USAGE);
|
|
160
|
+
const workspaces = await listWorkspaces();
|
|
161
|
+
const selected = workspaces.find((workspace) => workspace.slug === argv[0]);
|
|
162
|
+
if (!selected) {
|
|
163
|
+
throw new Error(`No workspace ${argv[0]}. You can reach: ${workspaces.map((workspace) => workspace.slug).sort().join(", ")}.`);
|
|
164
|
+
}
|
|
165
|
+
switchWorkspace(selected.slug);
|
|
166
|
+
return selected;
|
|
167
|
+
}
|
|
157
168
|
export async function workspaceRotateKey() {
|
|
158
169
|
const config = loadConfig();
|
|
159
170
|
const { api_key } = await rotateWorkspaceApiKey(config);
|