@shanesaravia/hive 0.3.0 → 0.4.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/CHANGELOG.md +21 -0
- package/README.md +16 -1
- package/node_modules/@hive/shared/dist/status.js +9 -0
- package/node_modules/@hive/shared/dist/types.d.ts +162 -1
- package/node_modules/@hive/shared/dist/types.js +27 -0
- package/package.json +1 -1
- package/packages/server/dist/agents/agentDiscovery.js +64 -0
- package/packages/server/dist/api/rest.js +474 -14
- package/packages/server/dist/api/ws.js +66 -2
- package/packages/server/dist/control/missionQuiesce.js +66 -0
- package/packages/server/dist/health/deriveAlerts.js +8 -0
- package/packages/server/dist/index.js +26 -2
- package/packages/server/dist/loops/loopCommand.js +56 -0
- package/packages/server/dist/loops/loopNoop.js +38 -0
- package/packages/server/dist/loops/loopScheduler.js +58 -0
- package/packages/server/dist/loops/loopStore.js +118 -0
- package/packages/server/dist/loops/monitors.js +38 -0
- package/packages/server/dist/messages/attachmentStore.js +92 -0
- package/packages/server/dist/messages/messagesStore.js +97 -33
- package/packages/server/dist/reviews/reviewDiff.js +47 -0
- package/packages/server/dist/roster/replyAsk.js +62 -0
- package/packages/server/dist/roster/rosterBuilder.js +41 -5
- package/packages/server/dist/roster/workerIdentity.js +46 -9
- package/packages/server/dist/skills/skillDiscovery.js +28 -4
- package/packages/server/dist/terminals/claudeStreamClient.js +90 -0
- package/packages/server/dist/terminals/codexAppServerClient.js +195 -0
- package/packages/server/dist/terminals/providerDetection.js +27 -0
- package/packages/server/dist/terminals/terminalCapability.js +45 -0
- package/packages/server/dist/terminals/terminalFeatures.js +11 -0
- package/packages/server/dist/terminals/terminalObservability.js +21 -0
- package/packages/server/dist/terminals/terminalRuntime.js +125 -0
- package/packages/server/dist/terminals/terminalStream.js +30 -0
- package/packages/server/dist/transcripts/transcriptReader.js +345 -0
- package/packages/web/dist/assets/index-DWjqiitn.js +17 -0
- package/packages/web/dist/assets/index-rd4RnLqj.css +2 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-BpEYVjCF.css +0 -2
- package/packages/web/dist/assets/index-rIAIJyuF.js +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Hive will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.4.0 — 2026-09-02
|
|
6
|
+
|
|
7
|
+
Makes the conversation trustworthy to read, gives Hive loops it can actually turn off, and settles two places where a mission misreported what it was doing.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Images pasted or dropped into a mission conversation. They upload as you type rather than on send, render as thumbnails in the message, and reach the agent as a path on disk to read — the one route that behaves the same in a repository, a worktree, or anywhere else.
|
|
12
|
+
- `@agent` completion in the composer, from the `.claude/agents` definitions, by the name each declares rather than its filename, with a project definition shadowing a user one exactly as the CLI resolves them.
|
|
13
|
+
- Loops Hive owns: `/loop 10m …` registers a recurring prompt Hive re-sends, kept in the database so it survives a restart instead of dying quietly while it still looks like it is running. `/loop list`, `/loop stop [id]` and `/tasks` in any conversation; a "N running" pill in the header, drawn only when something is, opening a panel that lists every loop and background monitor across the fleet with a stop on each.
|
|
14
|
+
- Consecutive loop passes that find nothing fold into one row in the transcript, so a poller that finds nothing overnight cannot bury the two runs that mattered. The row expands to the individual passes.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- A message could appear and then be replaced by a different one. A turn's reply row was rewritten on every poll as the turn progressed, progress notes were keyed by list position so a filtered entry slid every following one onto other text, and timestamps were rewritten, moving already-read messages. Text now only ever grows in place; anything genuinely different is appended.
|
|
19
|
+
- New messages no longer pull the viewport to the bottom. Scrolled up, you keep your place and are offered the way back.
|
|
20
|
+
- A decision no longer replaces the conversation it refers to. It is drawn inline where it was raised, with one line above the transcript that flags an unaddressed decision and walks between them when there are several.
|
|
21
|
+
- A turn that ends by asking you something — rather than through a structured prompt — is now an answerable decision instead of a mission that looks finished while it waits.
|
|
22
|
+
- A mission whose workers were running reported "Idle": a manager's session reads idle between its own turns while the subagents it dispatched keep working.
|
|
23
|
+
- Skill and agent discovery no longer fail outright when a mission's recorded repository is gone, which is every mission whose worktree has been reclaimed. Your own skills and agents apply regardless of where a mission runs. This also made such a mission impossible to message at all, because the send path discovered skills before falling back to a live repository.
|
|
24
|
+
- The composer takes focus when a mission opens.
|
|
25
|
+
|
|
5
26
|
## 0.3.0 — 2026-08-29
|
|
6
27
|
|
|
7
28
|
Turns a mission's full page into a dashboard, carries the keyboard into it, and settles a run of places where two parts of the board disagreed about one fact.
|
package/README.md
CHANGED
|
@@ -179,12 +179,14 @@ The selected working directory matters. The provider CLI is launched from that d
|
|
|
179
179
|
|
|
180
180
|
## Continue a conversation
|
|
181
181
|
|
|
182
|
-
Open a mission card and send a follow-up from the Conversation panel. Both providers preserve their native conversation/thread identity while Hive records each turn under the same durable mission.
|
|
182
|
+
Open a mission card and send a follow-up from the Conversation panel. Both providers preserve their native conversation/thread identity while Hive records each turn under the same durable mission. Conversation defaults to a quiet narrative that interleaves messages with plan, task, worker, verification, gate, and review milestones; switch to **Messages only** when you want an unaugmented transcript. Historical missions are not given synthetic milestones—stored events are shown as recorded, and future transitions are enriched durably.
|
|
183
183
|
|
|
184
184
|
Type `/` in either the new-mission prompt or an existing conversation to search the skills available to that provider in the selected working directory. Hive reads Claude skills for Claude missions and Codex skills for Codex missions; slash-form Codex selections are translated to the provider's native invocation form behind the scenes. Recognized skills are highlighted and sent as ordinary prompt text, leaving the provider CLI responsible for execution and permissions. Hive discovers skill metadata only; it does not preload entire skill bodies into mission context.
|
|
185
185
|
|
|
186
186
|
If a provider or deliberately interactive skill asks a structured question, Hive surfaces it in **Needs you** and the mission conversation. Answering there resumes the same mission; skill prompts are not silently bypassed.
|
|
187
187
|
|
|
188
|
+
**Needs you** also supports focused `j`/`k` triage, snoozing, and deferral until an item materially changes. Awaiting-acceptance missions expose a review packet with observed files, verification, gates, outputs, risks, source-control context, freshness protection, and actions to request changes, add or waive gates, rerun verification, or accept. Pin up to four missions into the persistent workspace with `Shift+Enter` or the command palette; `Cmd/Ctrl+1…4` focuses panes, and pane order, density, and split are retained locally.
|
|
189
|
+
|
|
188
190
|
## Navigate the fleet
|
|
189
191
|
|
|
190
192
|
The fleet is organized around attention rather than process creation time:
|
|
@@ -284,6 +286,19 @@ Health alerts explain the condition, supporting evidence, and a suggested recove
|
|
|
284
286
|
|
|
285
287
|
Mission and task controls include pause/resume, graceful or confirmed force stop, manager replacement, retry, cancellation, worker replacement/reassignment, priority changes, plan revision, fresh review, verification pass, completion, reopening, archive, and permanent deletion. Control actions are recorded in the activity timeline.
|
|
286
288
|
|
|
289
|
+
### Conversation and Terminal
|
|
290
|
+
|
|
291
|
+
Conversation is the primary structured mission interface. Terminal provides chronological provider output as a debugging and migration fallback. Claude manager sources currently send clearly labelled provider follow-ups; supported Codex manager sources can start an interactive app-server turn. Worker, completed, unsupported, busy, and disconnected sources stay readable but do not present a writable prompt.
|
|
292
|
+
|
|
293
|
+
Terminal input uses Enter to send and Shift+Enter for a new line. Large pastes require confirmation, control sequences are sanitized from transcript rendering, and interrupting a turn is distinct from pausing or stopping its mission. Search, plain-text copy, source selection, scroll position, and drafts work independently per mission source.
|
|
294
|
+
|
|
295
|
+
Recovery order is: return to Conversation, refresh the durable transcript, reload Hive to reconnect, interrupt the current interactive turn, then use the mission Pause/Stop controls if necessary. Writable modes can be disabled independently without disabling transcript viewing:
|
|
296
|
+
|
|
297
|
+
- `HIVE_TERMINAL_CODEX_INPUT=0` disables Codex interactive Terminal input.
|
|
298
|
+
- `HIVE_TERMINAL_CLAUDE_FOLLOW_UP=0` disables Claude follow-ups from Terminal; Conversation remains available.
|
|
299
|
+
|
|
300
|
+
Hive records local, privacy-safe Terminal audit metadata and aggregate counters—provider, mission/source identity, character count, outcome, reconnects, failures, and cleanup. It never adds raw Terminal input, credentials, or environment values to those records. Current local status is available at `GET /api/terminal/status`.
|
|
301
|
+
|
|
287
302
|
## Reports and GitHub
|
|
288
303
|
|
|
289
304
|
The Technical view exports a mission report as Markdown or JSON. Reports include the request, plan, decisions, workers, changed files, verification, outputs, resource use, conversation, and remaining risks. With a locally authenticated `gh` CLI, Hive can post a report to a GitHub issue or pull request after explicit confirmation; it does not publish automatically.
|
|
@@ -57,6 +57,15 @@ export function deriveStatus(session, job, recentEvents = [], now = Date.now())
|
|
|
57
57
|
if (blockedEventIsCurrent) {
|
|
58
58
|
return { status: "waiting_on_you", stale: false };
|
|
59
59
|
}
|
|
60
|
+
// A job the runtime still calls working is working, even while its own
|
|
61
|
+
// session record reads idle. A manager sits quiet between turns while the
|
|
62
|
+
// subagents it dispatched keep running, and that gap was presenting as an
|
|
63
|
+
// idle mission on a board where workers were visibly reporting progress.
|
|
64
|
+
// A job left "working" by a dead process ages out into stalled upstream,
|
|
65
|
+
// where the elapsed time is known.
|
|
66
|
+
if (STALE_JOB_STATES.has(job?.state ?? "")) {
|
|
67
|
+
return { status: "working", stale: false };
|
|
68
|
+
}
|
|
60
69
|
if (job?.state === "done") {
|
|
61
70
|
return { status: "done", stale: false };
|
|
62
71
|
}
|
|
@@ -76,6 +76,17 @@ export interface ClaudeJob {
|
|
|
76
76
|
backend?: string;
|
|
77
77
|
template?: string;
|
|
78
78
|
}
|
|
79
|
+
/** An image pasted into a mission conversation and written to disk for the agent to read. */
|
|
80
|
+
export interface MessageAttachment {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
mediaType: string;
|
|
84
|
+
bytes: number;
|
|
85
|
+
/** Absolute path on the Hive host — what the agent is told to open. */
|
|
86
|
+
path: string;
|
|
87
|
+
/** Server route the web UI renders the thumbnail from. */
|
|
88
|
+
url: string;
|
|
89
|
+
}
|
|
79
90
|
export interface MissionMessage {
|
|
80
91
|
id: string;
|
|
81
92
|
role: "user" | "assistant";
|
|
@@ -84,6 +95,13 @@ export interface MissionMessage {
|
|
|
84
95
|
jobId: string;
|
|
85
96
|
/** `update`: something the agent said on the way to its reply, shown compactly. */
|
|
86
97
|
kind?: "reply" | "update";
|
|
98
|
+
attachments?: MessageAttachment[];
|
|
99
|
+
/**
|
|
100
|
+
* A scheduled loop pass that found nothing to do. Consecutive quiet runs
|
|
101
|
+
* collapse into one row in the transcript so they cannot bury the runs that
|
|
102
|
+
* mattered — the messages themselves are still kept, and still readable.
|
|
103
|
+
*/
|
|
104
|
+
noop?: boolean;
|
|
87
105
|
}
|
|
88
106
|
/** @deprecated Use MissionMessage. */
|
|
89
107
|
export type ThreadMessage = MissionMessage;
|
|
@@ -184,6 +202,64 @@ export interface ClaudeSkillSummary {
|
|
|
184
202
|
argumentHint?: string;
|
|
185
203
|
source: "project" | "user" | "plugin" | "built-in" | "claude";
|
|
186
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* A subagent persona the conversation can address by name with `@qa-engineer`.
|
|
207
|
+
*
|
|
208
|
+
* These are the `.md` files under `.claude/agents`, the same ones the CLI
|
|
209
|
+
* offers — Hive discovers them so the composer can complete them, and passes
|
|
210
|
+
* the mention through untouched for the session's own agent loader to resolve.
|
|
211
|
+
*/
|
|
212
|
+
export interface ClaudeAgentSummary {
|
|
213
|
+
name: string;
|
|
214
|
+
description?: string;
|
|
215
|
+
source: "project" | "user";
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* A recurring prompt Hive re-sends to a mission on an interval.
|
|
219
|
+
*
|
|
220
|
+
* Hive owns the schedule rather than delegating to the agent's own `/loop`:
|
|
221
|
+
* that one lives inside a session's runtime with nothing on disk, so it can be
|
|
222
|
+
* neither listed nor stopped from outside. A loop you cannot turn off is worse
|
|
223
|
+
* than no loop at all.
|
|
224
|
+
*/
|
|
225
|
+
export interface MissionLoop {
|
|
226
|
+
id: string;
|
|
227
|
+
missionId: string;
|
|
228
|
+
missionName?: string;
|
|
229
|
+
prompt: string;
|
|
230
|
+
intervalMs: number;
|
|
231
|
+
status: "active" | "stopped";
|
|
232
|
+
createdAt: number;
|
|
233
|
+
/** Absent until the loop has fired at least once. */
|
|
234
|
+
lastRunAt?: number;
|
|
235
|
+
nextRunAt: number;
|
|
236
|
+
runCount: number;
|
|
237
|
+
/** Why it stopped, when it stopped itself rather than being stopped. */
|
|
238
|
+
stoppedReason?: string;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* One background watcher a mission's agent started and has not finished.
|
|
242
|
+
*
|
|
243
|
+
* Named, because "3 monitors running" tells you nothing about whether the one
|
|
244
|
+
* you care about is among them. The runtime records each in the job's fan with
|
|
245
|
+
* its own id and the label the agent gave it.
|
|
246
|
+
*/
|
|
247
|
+
export interface MissionMonitor {
|
|
248
|
+
missionId: string;
|
|
249
|
+
missionName: string;
|
|
250
|
+
/** The turn that owns it — the only thing that can actually be stopped. */
|
|
251
|
+
jobId: string;
|
|
252
|
+
/** The runtime's id for this watcher. */
|
|
253
|
+
id: string;
|
|
254
|
+
label: string;
|
|
255
|
+
startedAt: number;
|
|
256
|
+
/** The owning turn's latest note, for context. */
|
|
257
|
+
detail?: string;
|
|
258
|
+
}
|
|
259
|
+
export interface ActiveTasks {
|
|
260
|
+
loops: MissionLoop[];
|
|
261
|
+
monitors: MissionMonitor[];
|
|
262
|
+
}
|
|
187
263
|
export interface NativeCommand {
|
|
188
264
|
name: string;
|
|
189
265
|
description: string;
|
|
@@ -197,6 +273,91 @@ export interface NativeCommand {
|
|
|
197
273
|
* Only commands verified to print a text report in headless mode belong here.
|
|
198
274
|
*/
|
|
199
275
|
export declare const NATIVE_COMMANDS: NativeCommand[];
|
|
276
|
+
/**
|
|
277
|
+
* Skills compiled into the Claude CLI itself, rather than installed under
|
|
278
|
+
* `.claude/skills` or a plugin cache.
|
|
279
|
+
*
|
|
280
|
+
* Skill discovery walks the filesystem, so it can only ever find skills that
|
|
281
|
+
* exist as files — a built-in has no `SKILL.md` anywhere on disk and is
|
|
282
|
+
* therefore invisible to it. Offering them means naming them.
|
|
283
|
+
*
|
|
284
|
+
* Unlike `NATIVE_COMMANDS` these are *not* intercepted: they are ordinary
|
|
285
|
+
* prompts that the session's own skill loader handles, so they go down the
|
|
286
|
+
* normal send path with their arguments intact. `matchNativeCommand` only ever
|
|
287
|
+
* claims a bare `/name` that appears in `NATIVE_COMMANDS`, so nothing here is
|
|
288
|
+
* at risk of being run out-of-band by mistake.
|
|
289
|
+
*/
|
|
290
|
+
export declare const BUILTIN_SKILLS: ClaudeSkillSummary[];
|
|
291
|
+
/** One readable transcript belonging to a mission. */
|
|
292
|
+
export interface TerminalCapability {
|
|
293
|
+
/** Interactive writes target a live channel; follow_up starts/resumes a provider turn. */
|
|
294
|
+
mode: "interactive" | "follow_up" | "read_only";
|
|
295
|
+
connected: boolean;
|
|
296
|
+
acceptsInterrupt: boolean;
|
|
297
|
+
acceptsResize: boolean;
|
|
298
|
+
provider?: MissionProvider;
|
|
299
|
+
providerVersion?: string;
|
|
300
|
+
/** Detected locally but not yet promoted to Hive's production input path. */
|
|
301
|
+
detectedInteractiveTransport?: "codex_app_server" | "claude_stream_json";
|
|
302
|
+
/** Human-readable explanation for a non-interactive mode. */
|
|
303
|
+
reason?: string;
|
|
304
|
+
}
|
|
305
|
+
export interface TerminalStreamEvent {
|
|
306
|
+
streamId: string;
|
|
307
|
+
sequence: number;
|
|
308
|
+
missionId: string;
|
|
309
|
+
source: string;
|
|
310
|
+
ts: number;
|
|
311
|
+
method: string;
|
|
312
|
+
params?: Record<string, unknown>;
|
|
313
|
+
}
|
|
314
|
+
export interface TerminalInputReceipt {
|
|
315
|
+
clientInputId: string;
|
|
316
|
+
status: "accepted" | "duplicate";
|
|
317
|
+
streamId: string;
|
|
318
|
+
sequence: number;
|
|
319
|
+
turnId?: string;
|
|
320
|
+
}
|
|
321
|
+
export interface TranscriptSource {
|
|
322
|
+
/** `orchestrator`, or the worker's native agent id. */
|
|
323
|
+
id: string;
|
|
324
|
+
label: string;
|
|
325
|
+
kind: "orchestrator" | "worker";
|
|
326
|
+
sessionId: string;
|
|
327
|
+
/** The subagent's declared type, when the provider recorded one. */
|
|
328
|
+
agentType?: string;
|
|
329
|
+
updatedAt?: number;
|
|
330
|
+
bytes?: number;
|
|
331
|
+
terminal?: TerminalCapability;
|
|
332
|
+
}
|
|
333
|
+
export type TranscriptEntryKind =
|
|
334
|
+
/** Something a person (or the orchestrator) actually asked for. */
|
|
335
|
+
"prompt"
|
|
336
|
+
/** Instructions the runtime injected into the turn: a skill, a reminder. */
|
|
337
|
+
| "injected" | "assistant" | "thinking" | "tool_use" | "tool_result";
|
|
338
|
+
/** One line of the transcript, shaped the way a terminal prints it. */
|
|
339
|
+
export interface TranscriptEntry {
|
|
340
|
+
kind: TranscriptEntryKind;
|
|
341
|
+
ts?: number;
|
|
342
|
+
/** Prose, for every kind but a tool call and its result. */
|
|
343
|
+
text?: string;
|
|
344
|
+
/** Tool name, for `tool_use`. */
|
|
345
|
+
tool?: string;
|
|
346
|
+
/** `Bash(npm test)` — the tool plus the one field that says what it is doing. */
|
|
347
|
+
invocation?: string;
|
|
348
|
+
/** The tool call's full input, pretty-printed, for the expanded view. */
|
|
349
|
+
input?: string;
|
|
350
|
+
/** The tool result's body, for `tool_result`. */
|
|
351
|
+
output?: string;
|
|
352
|
+
/** Lines in the untruncated output, so the UI can say what it collapsed. */
|
|
353
|
+
lines?: number;
|
|
354
|
+
isError?: boolean;
|
|
355
|
+
/** Pairs a result with the call it answers. */
|
|
356
|
+
toolUseId?: string;
|
|
357
|
+
/** The skill the runtime attributed this entry to, when it recorded one. */
|
|
358
|
+
skill?: string;
|
|
359
|
+
truncated?: boolean;
|
|
360
|
+
}
|
|
200
361
|
export type MissionLifecycleStatus = "active" | "ready_for_review" | "paused" | "completed" | "failed" | "archived";
|
|
201
362
|
export interface MissionMessagesPage {
|
|
202
363
|
messages: MissionMessage[];
|
|
@@ -322,7 +483,7 @@ export interface HiveEvent {
|
|
|
322
483
|
*/
|
|
323
484
|
backgrounded?: boolean;
|
|
324
485
|
/** For source: 'custom' — one of the orchestrator lifecycle phases. */
|
|
325
|
-
phase?: "delegating" | "worker_started" | "worker_reported" | "reviewing" | "blocked_on_user" | "ready_for_review" | "plan_updated" | "decision_resolved" | "resuming" | "custom";
|
|
486
|
+
phase?: "delegating" | "worker_started" | "worker_reported" | "reviewing" | "blocked_on_user" | "ready_for_review" | "plan_updated" | "phase_started" | "phase_completed" | "task_started" | "task_completed" | "gate_satisfied" | "gate_failed" | "gate_waived" | "decision_resolved" | "resuming" | "custom";
|
|
326
487
|
detail: string;
|
|
327
488
|
/** Best-effort display name for the attributed worker. */
|
|
328
489
|
targetWorker?: string;
|
|
@@ -12,3 +12,30 @@ export const NATIVE_COMMANDS = [
|
|
|
12
12
|
{ name: "cost", description: "Show the cost and duration breakdown for Claude usage", scope: "account" },
|
|
13
13
|
{ name: "context", description: "Show this mission session's context window usage", scope: "session" },
|
|
14
14
|
];
|
|
15
|
+
/**
|
|
16
|
+
* Skills compiled into the Claude CLI itself, rather than installed under
|
|
17
|
+
* `.claude/skills` or a plugin cache.
|
|
18
|
+
*
|
|
19
|
+
* Skill discovery walks the filesystem, so it can only ever find skills that
|
|
20
|
+
* exist as files — a built-in has no `SKILL.md` anywhere on disk and is
|
|
21
|
+
* therefore invisible to it. Offering them means naming them.
|
|
22
|
+
*
|
|
23
|
+
* Unlike `NATIVE_COMMANDS` these are *not* intercepted: they are ordinary
|
|
24
|
+
* prompts that the session's own skill loader handles, so they go down the
|
|
25
|
+
* normal send path with their arguments intact. `matchNativeCommand` only ever
|
|
26
|
+
* claims a bare `/name` that appears in `NATIVE_COMMANDS`, so nothing here is
|
|
27
|
+
* at risk of being run out-of-band by mistake.
|
|
28
|
+
*/
|
|
29
|
+
export const BUILTIN_SKILLS = [
|
|
30
|
+
{
|
|
31
|
+
name: "loop",
|
|
32
|
+
description: "Re-send a prompt to this mission on a schedule Hive owns, so it can be listed and stopped",
|
|
33
|
+
argumentHint: "[interval] <prompt> · list · stop [id]",
|
|
34
|
+
source: "built-in",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "tasks",
|
|
38
|
+
description: "Show every loop and background monitor running, here and across the fleet",
|
|
39
|
+
source: "built-in",
|
|
40
|
+
},
|
|
41
|
+
];
|
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { requireWorkingDirectory } from "../paths/pathResolver.js";
|
|
5
|
+
/**
|
|
6
|
+
* An agent declares its own name in frontmatter; the filename is only a
|
|
7
|
+
* fallback, because the two are allowed to disagree and the CLI resolves a
|
|
8
|
+
* mention against the declared name.
|
|
9
|
+
*/
|
|
10
|
+
function metadata(file, fallback) {
|
|
11
|
+
let text = "";
|
|
12
|
+
try {
|
|
13
|
+
text = fs.readFileSync(file, "utf8").slice(0, 8192);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { name: fallback };
|
|
17
|
+
}
|
|
18
|
+
const frontmatter = text.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
19
|
+
const field = (name) => frontmatter?.[1].match(new RegExp(`^${name}:\\s*(.+)$`, "m"))?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
20
|
+
return { name: field("name") || fallback, description: field("description") };
|
|
21
|
+
}
|
|
22
|
+
function collect(root, source) {
|
|
23
|
+
if (!fs.existsSync(root))
|
|
24
|
+
return [];
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return entries.flatMap((entry) => {
|
|
33
|
+
if (entry.name.startsWith(".") || !entry.isFile() || !entry.name.endsWith(".md"))
|
|
34
|
+
return [];
|
|
35
|
+
return [{ ...metadata(path.join(root, entry.name), path.basename(entry.name, ".md")), source }];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Project agents shadow user agents of the same name, matching how the CLI
|
|
40
|
+
* resolves them — the more specific definition is the one a mention will hit,
|
|
41
|
+
* so it is the one the composer should offer.
|
|
42
|
+
*
|
|
43
|
+
* A missing or unusable working directory is not an error here. A mission
|
|
44
|
+
* whose worktree has been reclaimed still resolves `@reviewer` against the
|
|
45
|
+
* user's own agents, and failing the whole lookup left the composer with
|
|
46
|
+
* nothing to complete at all — while `/` kept working, because its list has
|
|
47
|
+
* built-in entries that never depended on discovery.
|
|
48
|
+
*/
|
|
49
|
+
export function discoverAgents(cwd) {
|
|
50
|
+
const claudeConfig = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
51
|
+
let projectAgents = [];
|
|
52
|
+
try {
|
|
53
|
+
projectAgents = collect(path.join(requireWorkingDirectory(cwd), ".claude", "agents"), "project");
|
|
54
|
+
}
|
|
55
|
+
catch { /* no usable project directory; the user's own agents still apply */ }
|
|
56
|
+
const all = [
|
|
57
|
+
...collect(path.join(claudeConfig, "agents"), "user"),
|
|
58
|
+
...projectAgents,
|
|
59
|
+
];
|
|
60
|
+
const unique = new Map();
|
|
61
|
+
for (const agent of all)
|
|
62
|
+
unique.set(agent.name, agent);
|
|
63
|
+
return [...unique.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
64
|
+
}
|