@shanesaravia/hive 0.2.1 → 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 +53 -0
- package/README.md +18 -1
- package/node_modules/@hive/shared/dist/directStudio.d.ts +6 -0
- package/node_modules/@hive/shared/dist/directStudio.js +12 -0
- package/node_modules/@hive/shared/dist/index.d.ts +2 -0
- package/node_modules/@hive/shared/dist/index.js +2 -0
- package/node_modules/@hive/shared/dist/reviewHall.d.ts +15 -0
- package/node_modules/@hive/shared/dist/reviewHall.js +49 -0
- package/node_modules/@hive/shared/dist/status.js +9 -0
- package/node_modules/@hive/shared/dist/types.d.ts +191 -1
- package/node_modules/@hive/shared/dist/types.js +27 -0
- package/node_modules/@hive/shared/dist/workers.d.ts +14 -0
- package/node_modules/@hive/shared/dist/workers.js +22 -0
- package/package.json +1 -1
- package/packages/server/dist/agents/agentDiscovery.js +64 -0
- package/packages/server/dist/api/rest.js +538 -23
- package/packages/server/dist/api/ws.js +90 -9
- package/packages/server/dist/control/launcher.js +50 -11
- package/packages/server/dist/control/messaging.js +3 -2
- package/packages/server/dist/control/missionQuiesce.js +66 -0
- package/packages/server/dist/health/deriveAlerts.js +9 -2
- package/packages/server/dist/hooks/hookIngest.js +69 -10
- package/packages/server/dist/index.js +44 -4
- 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 +110 -33
- package/packages/server/dist/missions/missionsStore.js +9 -0
- package/packages/server/dist/missions/reopenOnWork.js +20 -0
- package/packages/server/dist/plans/planReconcile.js +114 -0
- package/packages/server/dist/plans/plansStore.js +46 -3
- package/packages/server/dist/reviews/reviewDiff.js +47 -0
- package/packages/server/dist/roster/missionReplay.js +82 -0
- package/packages/server/dist/roster/replyAsk.js +62 -0
- package/packages/server/dist/roster/rosterBuilder.js +78 -147
- package/packages/server/dist/roster/workerIdentity.js +923 -0
- 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/server/dist/watch/jobsWatcher.js +55 -24
- 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/templates/agents/hive-orchestrator.md +1 -0
- package/packages/web/dist/assets/index-Bzle5Xla.css +0 -2
- package/packages/web/dist/assets/index-C6AY0vYC.js +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
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
|
+
|
|
26
|
+
## 0.3.0 — 2026-08-29
|
|
27
|
+
|
|
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.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- A mission's full page is a dashboard rather than five tabs: the objective and its acceptance criteria, the conversation, and what is happening now sit in three columns that cannot hide each other, with the plan full width beneath them and diagnostics folded away at the foot.
|
|
33
|
+
- Progress measures that say how far along a mission is — phase and gate progress and the files that actually changed — replacing elapsed time, a worker count and the same worker count again.
|
|
34
|
+
- Keyboard control of a mission's own page: `a` and `d` do what the mission is asking, `r` reaches the composer, `p` pauses, `g c` / `g w` / `g p` / `g d` / `g g` jump around the page, `h` and `l` walk the missions still yours to do, and `⇧H` / `⇧L` walk only the ones asking for you.
|
|
35
|
+
- Bulk **Complete** and **Delete**, with `⇧C`, `⇧P`, `⇧A` and `⇧⌫` on the selection bar and `⏎` to commit a confirmation. Archive and Delete arm a confirmation rather than firing, so nothing irreversible happens on a keystroke.
|
|
36
|
+
- Click-to-copy on a mission's repository path, branch and id, and a note on returning to a mission of what arrived while you were away.
|
|
37
|
+
- A **Settings** menu for sound, notifications, workspace policy and templates.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- One page width for the board, the office and both mission presentations, which had grown three between them.
|
|
42
|
+
- The Needs you rail leads the board and reads as the most important thing on it; running work now sits above work awaiting acceptance, which the rail directly above it already lists.
|
|
43
|
+
- Bulk Accept and Complete became one **Complete**, over running work and work awaiting acceptance alike, and every bulk control appears only when it applies to the selection.
|
|
44
|
+
- Filters survive a reload, as the sort beside them already did, and the board's shortcuts keep working in the office.
|
|
45
|
+
- **Request changes** asks what should change instead of quietly reopening the mission, and a mission awaiting acceptance reopens when it starts working again rather than because you spoke to it.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Background sessions launch in `auto`. `bypassPermissions` now requires a one-time interactive disclaimer that a server cannot give on the user's behalf, so every mission launch was failing; a launch that is refused now reports the CLI's own words in about a second instead of a thirty-second timeout.
|
|
50
|
+
- One owner for Escape. Ten independent listeners each guessed whether the key was theirs, so two open slide-overs closed together on one press.
|
|
51
|
+
- Clicking a worker opens that worker beside the board, instead of promoting the whole board to a full page and dropping the worker on the way.
|
|
52
|
+
- The office counts the missions the board says need you. Its floor badges and its avatars each used a different rule, and neither counted a pending decision or an unapproved plan.
|
|
53
|
+
- A card no longer asks you to approve a plan whose work has already run, and a reopened mission stops reporting that it awaits an acceptance.
|
|
54
|
+
- The activity line's tense follows the evidence — `Ran` for a finished command, `Running` only while one is open — and it names a worker's own events rather than labelling them "Worker activity".
|
|
55
|
+
- A stale mission is called stale in the rail, as it already was in its pill and in the filter.
|
|
56
|
+
- Cards keep one shape whatever state they are in, rows end level, and a card's standing summary clamps to the two lines it was always meant to.
|
|
57
|
+
|
|
5
58
|
## 0.2.1 — 2026-08-25
|
|
6
59
|
|
|
7
60
|
### Added
|
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.
|
|
@@ -428,6 +443,8 @@ Project planning is split by scope:
|
|
|
428
443
|
- [`docs/IMPROVEMENTS.md`](docs/IMPROVEMENTS.md) — core product roadmap and completion checklist
|
|
429
444
|
- [`docs/VISUAL_OFFICE_PLAN.md`](docs/VISUAL_OFFICE_PLAN.md) — completed visual-office foundation
|
|
430
445
|
- [`docs/VISUAL_OFFICE_FEEL_ALIVE_PLAN.md`](docs/VISUAL_OFFICE_FEEL_ALIVE_PLAN.md) — current and upcoming atmosphere, interaction, and polish phases
|
|
446
|
+
- [`docs/AGENT_SYNC_AND_OFFICE_STATE_PLAN.md`](docs/AGENT_SYNC_AND_OFFICE_STATE_PLAN.md) — audit and phased plan for 1:1 terminal-agent sync, canonical worker identity, and checkpointed office animation state
|
|
447
|
+
- [`docs/OFFICE_STABILITY_AUDIT.md`](docs/OFFICE_STABILITY_AUDIT.md) — replay-driven audit of the backend → office pipeline, with the tiered plan for a dependable roster and animation scene
|
|
431
448
|
|
|
432
449
|
## Troubleshooting
|
|
433
450
|
|
|
@@ -4,4 +4,10 @@ import type { OrchestratorNode } from "./types.js";
|
|
|
4
4
|
* Bulk cleanup must never accept gates or dismiss a mission that still needs
|
|
5
5
|
* work or user attention.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* A direct agent has finished when its turn is over and nothing is pending:
|
|
9
|
+
* its desk goes green with the check, and it stays seated until the user
|
|
10
|
+
* marks it complete — leaving at once would take its reply with it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isDirectMissionFinished(node: OrchestratorNode): boolean;
|
|
7
13
|
export declare function canClearDirectStudioMission(node: OrchestratorNode): boolean;
|
|
@@ -4,6 +4,18 @@ const CLEARABLE_ACTIVITY = new Set(["idle", "done", "offline"]);
|
|
|
4
4
|
* Bulk cleanup must never accept gates or dismiss a mission that still needs
|
|
5
5
|
* work or user attention.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* A direct agent has finished when its turn is over and nothing is pending:
|
|
9
|
+
* its desk goes green with the check, and it stays seated until the user
|
|
10
|
+
* marks it complete — leaving at once would take its reply with it.
|
|
11
|
+
*/
|
|
12
|
+
export function isDirectMissionFinished(node) {
|
|
13
|
+
if (node.mission.mode !== "direct" || node.lifecycleStatus !== "active" || !node.turnCompleted)
|
|
14
|
+
return false;
|
|
15
|
+
if (!["idle", "done"].includes(node.activityStatus) || node.inFlight.tasks > 0 || node.inFlight.queued > 0)
|
|
16
|
+
return false;
|
|
17
|
+
return node.alerts.every((alert) => alert.severity !== "critical");
|
|
18
|
+
}
|
|
7
19
|
export function canClearDirectStudioMission(node) {
|
|
8
20
|
if (node.mission.mode !== "direct" || node.lifecycleStatus !== "active")
|
|
9
21
|
return false;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OrchestratorNode } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a mission awaiting acceptance can be accepted without a human
|
|
4
|
+
* reading it first.
|
|
5
|
+
*
|
|
6
|
+
* Acceptance is the human sign-off that `ready_for_review` exists to require,
|
|
7
|
+
* so a bulk action must only ever cover missions where there is demonstrably
|
|
8
|
+
* nothing to judge: every required gate is satisfied or explicitly waived,
|
|
9
|
+
* nothing is asking a question, no alert is open, and no work is still moving.
|
|
10
|
+
* Anything else keeps its seat and has to be accepted individually — a bulk
|
|
11
|
+
* button must never turn the review step into a rubber stamp.
|
|
12
|
+
*/
|
|
13
|
+
export declare function canAcceptReviewMission(node: OrchestratorNode, hasPendingDecision?: boolean): boolean;
|
|
14
|
+
/** Why a seated mission was left for the user to accept themselves. */
|
|
15
|
+
export declare function reviewAcceptanceBlocker(node: OrchestratorNode, hasPendingDecision?: boolean): string | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a mission awaiting acceptance can be accepted without a human
|
|
3
|
+
* reading it first.
|
|
4
|
+
*
|
|
5
|
+
* Acceptance is the human sign-off that `ready_for_review` exists to require,
|
|
6
|
+
* so a bulk action must only ever cover missions where there is demonstrably
|
|
7
|
+
* nothing to judge: every required gate is satisfied or explicitly waived,
|
|
8
|
+
* nothing is asking a question, no alert is open, and no work is still moving.
|
|
9
|
+
* Anything else keeps its seat and has to be accepted individually — a bulk
|
|
10
|
+
* button must never turn the review step into a rubber stamp.
|
|
11
|
+
*/
|
|
12
|
+
export function canAcceptReviewMission(node, hasPendingDecision = false) {
|
|
13
|
+
if (node.lifecycleStatus !== "ready_for_review")
|
|
14
|
+
return false;
|
|
15
|
+
if (hasPendingDecision)
|
|
16
|
+
return false;
|
|
17
|
+
if (node.activityStatus === "waiting_on_you" || node.activityStatus === "error" || node.activityStatus === "stalled")
|
|
18
|
+
return false;
|
|
19
|
+
if (node.inFlight.tasks > 0 || node.inFlight.queued > 0)
|
|
20
|
+
return false;
|
|
21
|
+
if (node.workers.some((worker) => !worker.doneAt && (worker.jobState === "working" || worker.jobState === "busy")))
|
|
22
|
+
return false;
|
|
23
|
+
if (node.alerts.length > 0)
|
|
24
|
+
return false;
|
|
25
|
+
return !(node.plan?.gates ?? []).some((gate) => gate.required && gate.status !== "satisfied" && gate.status !== "waived");
|
|
26
|
+
}
|
|
27
|
+
/** Why a seated mission was left for the user to accept themselves. */
|
|
28
|
+
export function reviewAcceptanceBlocker(node, hasPendingDecision = false) {
|
|
29
|
+
if (node.lifecycleStatus !== "ready_for_review")
|
|
30
|
+
return "not awaiting acceptance";
|
|
31
|
+
if (hasPendingDecision)
|
|
32
|
+
return "waiting on your answer";
|
|
33
|
+
if (node.activityStatus === "waiting_on_you")
|
|
34
|
+
return "waiting on you";
|
|
35
|
+
if (node.activityStatus === "error")
|
|
36
|
+
return "runtime error";
|
|
37
|
+
if (node.activityStatus === "stalled")
|
|
38
|
+
return "work stalled";
|
|
39
|
+
if (node.inFlight.tasks > 0 || node.inFlight.queued > 0)
|
|
40
|
+
return "work still in flight";
|
|
41
|
+
if (node.workers.some((worker) => !worker.doneAt && (worker.jobState === "working" || worker.jobState === "busy")))
|
|
42
|
+
return "a worker is still running";
|
|
43
|
+
if (node.alerts.length > 0)
|
|
44
|
+
return node.alerts.length === 1 ? "1 open alert" : `${node.alerts.length} open alerts`;
|
|
45
|
+
const unmet = (node.plan?.gates ?? []).filter((gate) => gate.required && gate.status !== "satisfied" && gate.status !== "waived");
|
|
46
|
+
if (unmet.length)
|
|
47
|
+
return unmet.length === 1 ? `unmet gate: ${unmet[0].label}` : `${unmet.length} unmet gates`;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
@@ -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
|
}
|
|
@@ -57,6 +57,8 @@ export interface ClaudeJob {
|
|
|
57
57
|
* needs/detail — the full conversational reply lives in the timeline.
|
|
58
58
|
*/
|
|
59
59
|
lastText?: string;
|
|
60
|
+
/** Hive-augmented: what the model said between tool calls this turn, oldest first. */
|
|
61
|
+
progressTexts?: string[];
|
|
60
62
|
children: JobChild[];
|
|
61
63
|
intent?: string;
|
|
62
64
|
name?: string;
|
|
@@ -74,12 +76,32 @@ export interface ClaudeJob {
|
|
|
74
76
|
backend?: string;
|
|
75
77
|
template?: string;
|
|
76
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
|
+
}
|
|
77
90
|
export interface MissionMessage {
|
|
78
91
|
id: string;
|
|
79
92
|
role: "user" | "assistant";
|
|
80
93
|
text: string;
|
|
81
94
|
createdAt: number;
|
|
82
95
|
jobId: string;
|
|
96
|
+
/** `update`: something the agent said on the way to its reply, shown compactly. */
|
|
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;
|
|
83
105
|
}
|
|
84
106
|
/** @deprecated Use MissionMessage. */
|
|
85
107
|
export type ThreadMessage = MissionMessage;
|
|
@@ -87,6 +109,22 @@ export interface WorkerActivity extends JobFanEntry {
|
|
|
87
109
|
jobId: string;
|
|
88
110
|
jobState: string;
|
|
89
111
|
jobUpdatedAt?: number;
|
|
112
|
+
/** Other provider ids and delegation labels that refer to this same worker. */
|
|
113
|
+
aliases?: string[];
|
|
114
|
+
/** Plan task ids delegated to this worker. */
|
|
115
|
+
taskIds?: string[];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Structured worker attribution for a hook firing. Replaces overloading one
|
|
119
|
+
* string with an agent id, an agent *type*, and a human label: only `agentId`
|
|
120
|
+
* may create a worker identity, and `parentAgentId` marks a nested agent whose
|
|
121
|
+
* activity belongs to its parent.
|
|
122
|
+
*/
|
|
123
|
+
export interface WorkerRef {
|
|
124
|
+
agentId?: string;
|
|
125
|
+
agentType?: string;
|
|
126
|
+
label?: string;
|
|
127
|
+
parentAgentId?: string;
|
|
90
128
|
}
|
|
91
129
|
export interface MissionSummary {
|
|
92
130
|
id: string;
|
|
@@ -164,6 +202,64 @@ export interface ClaudeSkillSummary {
|
|
|
164
202
|
argumentHint?: string;
|
|
165
203
|
source: "project" | "user" | "plugin" | "built-in" | "claude";
|
|
166
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
|
+
}
|
|
167
263
|
export interface NativeCommand {
|
|
168
264
|
name: string;
|
|
169
265
|
description: string;
|
|
@@ -177,6 +273,91 @@ export interface NativeCommand {
|
|
|
177
273
|
* Only commands verified to print a text report in headless mode belong here.
|
|
178
274
|
*/
|
|
179
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
|
+
}
|
|
180
361
|
export type MissionLifecycleStatus = "active" | "ready_for_review" | "paused" | "completed" | "failed" | "archived";
|
|
181
362
|
export interface MissionMessagesPage {
|
|
182
363
|
messages: MissionMessage[];
|
|
@@ -295,10 +476,19 @@ export interface HiveEvent {
|
|
|
295
476
|
command?: string;
|
|
296
477
|
artifactPath?: string;
|
|
297
478
|
outcome?: "success" | "failure";
|
|
479
|
+
/**
|
|
480
|
+
* The tool call left work running in the background. The provider ends the
|
|
481
|
+
* agent's turn and wakes it when that work finishes, so a worker with an
|
|
482
|
+
* outstanding backgrounded command has not finished — it is waiting.
|
|
483
|
+
*/
|
|
484
|
+
backgrounded?: boolean;
|
|
298
485
|
/** For source: 'custom' — one of the orchestrator lifecycle phases. */
|
|
299
|
-
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";
|
|
300
487
|
detail: string;
|
|
488
|
+
/** Best-effort display name for the attributed worker. */
|
|
301
489
|
targetWorker?: string;
|
|
490
|
+
/** Structured attribution; `workerRef.agentId` is the only identity source. */
|
|
491
|
+
workerRef?: WorkerRef;
|
|
302
492
|
targetTask?: string;
|
|
303
493
|
decisionId?: string;
|
|
304
494
|
decisionKind?: DecisionKind;
|
|
@@ -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
|
+
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WorkerActivity } from "./types.js";
|
|
2
|
+
/** The manager's own agent name. Never a worker on the floor. */
|
|
3
|
+
export declare const ORCHESTRATOR_AGENT_NAME = "hive-orchestrator";
|
|
4
|
+
/** Every string that refers to one canonical worker. */
|
|
5
|
+
export declare function workerRefs(worker: Pick<WorkerActivity, "id" | "label" | "aliases" | "taskIds">): string[];
|
|
6
|
+
/**
|
|
7
|
+
* The single worker-identity comparison for the whole app.
|
|
8
|
+
*
|
|
9
|
+
* The server resolves every provider name, delegation label, and plan task id
|
|
10
|
+
* for a worker into one canonical identity and publishes the rest as aliases,
|
|
11
|
+
* so consumers can compare exactly. Prefix and substring matching used to
|
|
12
|
+
* stand in for this and could silently merge two workers or split one.
|
|
13
|
+
*/
|
|
14
|
+
export declare function matchesWorker(target: string | undefined, worker: Pick<WorkerActivity, "id" | "label" | "aliases" | "taskIds">): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The manager's own agent name. Never a worker on the floor. */
|
|
2
|
+
export const ORCHESTRATOR_AGENT_NAME = "hive-orchestrator";
|
|
3
|
+
/** Every string that refers to one canonical worker. */
|
|
4
|
+
export function workerRefs(worker) {
|
|
5
|
+
return [worker.id, worker.label, ...(worker.aliases ?? []), ...(worker.taskIds ?? [])]
|
|
6
|
+
.map((value) => value?.trim())
|
|
7
|
+
.filter((value) => Boolean(value));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The single worker-identity comparison for the whole app.
|
|
11
|
+
*
|
|
12
|
+
* The server resolves every provider name, delegation label, and plan task id
|
|
13
|
+
* for a worker into one canonical identity and publishes the rest as aliases,
|
|
14
|
+
* so consumers can compare exactly. Prefix and substring matching used to
|
|
15
|
+
* stand in for this and could silently merge two workers or split one.
|
|
16
|
+
*/
|
|
17
|
+
export function matchesWorker(target, worker) {
|
|
18
|
+
const wanted = target?.trim().toLowerCase();
|
|
19
|
+
if (!wanted)
|
|
20
|
+
return false;
|
|
21
|
+
return workerRefs(worker).some((ref) => ref.toLowerCase() === wanted);
|
|
22
|
+
}
|
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
|
+
}
|