@staix/agent-hub 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +13 -4
- package/docs/agent-notes/adapters.md +4 -2
- package/docs/agent-notes/bus.md +4 -4
- package/docs/agent-notes/daemon.md +8 -4
- package/docs/agent-notes/tasks.md +7 -2
- package/docs/agent-notes/tests.md +6 -5
- package/docs/design/adapters.md +76 -0
- package/docs/design/agent-hub-architecture.md +67 -0
- package/docs/design/agent-hub.md +52 -0
- package/docs/design/bus.md +48 -0
- package/docs/design/task-board.md +86 -0
- package/docs/dogfood.md +19 -2
- package/docs/operations.md +337 -15
- package/docs/profiles.md +2 -0
- package/docs/quickstart.md +15 -3
- package/docs/sdlc.md +4 -4
- package/docs/security.md +9 -4
- package/docs/smoke.md +2 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +3 -1
- package/docs/specs/2026-11-01-research-campaigns-design.md +12 -1
- package/docs/verification/2026-10-31-codex-0166-reviewer-isolation.md +2 -0
- package/docs/verification/2026-11-01-0.27.0.md +49 -0
- package/docs/verification/2026-11-02-0.28.0.md +54 -0
- package/docs/verification/2026-11-02-autonomy-t4.md +39 -0
- package/docs/verification/2026-11-02-codex-0166-reviewer-tool-call.md +53 -0
- package/docs/verification/2026-11-02-control-write-after.json +139 -0
- package/docs/verification/2026-11-02-control-write-baseline.json +127 -0
- package/docs/verification/2026-11-02-control-write-guarded.json +127 -0
- package/docs/verification/2026-11-02-control-write-outcomes.md +65 -0
- package/docs/verification/2026-11-02-orca-1.4.245-cli-contract.md +51 -0
- package/docs/verified.json +13 -13
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +344 -116
- package/src/adapters/claude-channel.ts +12 -2
- package/src/adapters/compat.ts +1 -1
- package/src/adapters/definitions/native-cli-claude-setup.ts +31 -9
- package/src/adapters/definitions/native-cli-entry-jcode.ts +33 -0
- package/src/adapters/definitions/native-cli-entry-qwen.ts +2 -2
- package/src/adapters/jcode.ts +37 -4
- package/src/adapters/qwen-dual.ts +3 -2
- package/src/cli/agents.ts +4 -2
- package/src/cli/bench.ts +4 -2
- package/src/cli/board-read.ts +25 -0
- package/src/cli/claude-homes.ts +2 -2
- package/src/cli/console-state.ts +8 -3
- package/src/cli/help.ts +8 -6
- package/src/cli/identity.ts +14 -6
- package/src/cli/init-prompt.ts +179 -0
- package/src/cli/init-settings.ts +229 -0
- package/src/cli/init.ts +102 -9
- package/src/cli/launch.ts +3 -1
- package/src/cli/main.ts +112 -36
- package/src/cli/output.ts +14 -0
- package/src/cli/setup-homes.ts +18 -3
- package/src/cli/specs.ts +150 -0
- package/src/cli/study-output.ts +56 -0
- package/src/cli/study.ts +26 -3
- package/src/cli/terminal-recovery.ts +35 -11
- package/src/hub/action-policy.ts +5 -3
- package/src/hub/board.ts +56 -3
- package/src/hub/bus.ts +54 -7
- package/src/hub/child-process.ts +6 -4
- package/src/hub/config-apply.ts +4 -1
- package/src/hub/config-edit.ts +1 -1
- package/src/hub/config-tier.ts +1 -0
- package/src/hub/control-client.ts +28 -4
- package/src/hub/cost-governance.ts +22 -6
- package/src/hub/crash.ts +20 -0
- package/src/hub/daemon.ts +324 -64
- package/src/hub/delivery-hints.ts +6 -3
- package/src/hub/delivery-journal.ts +14 -3
- package/src/hub/events.ts +2 -1
- package/src/hub/hub-tools.ts +15 -9
- package/src/hub/no-writes.ts +1 -1
- package/src/hub/peers.ts +1 -1
- package/src/hub/project-runtime-state.ts +38 -13
- package/src/hub/review-brief.ts +18 -0
- package/src/hub/routing.ts +7 -3
- package/src/hub/sdlc-release-authorization.ts +11 -5
- package/src/hub/session-identity.ts +1 -1
- package/src/hub/settings.ts +2 -0
- package/src/hub/specs-config.ts +43 -0
- package/src/hub/specs-task.ts +108 -0
- package/src/hub/specs.ts +1008 -0
- package/src/hub/task-list-page.ts +75 -0
- package/src/hub/task-sweep.ts +10 -1
- package/src/hub/tasks.ts +448 -40
- package/src/hub/terminal-open.ts +10 -5
- package/src/hub/tool-request.ts +131 -0
- package/src/ui/office-model.ts +17 -1
- package/src/ui/office-space.ts +1 -1
- package/templates/config.json +1 -0
- package/templates/presets/confirmation-matrix.json +5 -5
- package/templates/profiles/dev.json +1 -0
- package/templates/profiles/general.json +1 -0
- package/templates/profiles/research.json +1 -0
- package/templates/profiles/writing.json +1 -0
- package/templates/release-guard.block.md +2 -1
- package/templates/specs.block.md +5 -0
|
@@ -6,19 +6,35 @@ export interface CostTask { repo: string; pr: number; private?: boolean; history
|
|
|
6
6
|
export interface CostPr { repo: string; pr: number; state: "OPEN" | "CLOSED" | "MERGED"; mergedAt?: number; complete: boolean; ciRuns?: { id: number; attempt: number }[]; reviews?: number }
|
|
7
7
|
export interface CostRow { repo: string; pr: number; reviewRounds: number; ciRuns: number | null; ciReruns: number | null; fullReReviews: number; deltaReviews: number; reviewMs: number | null; unknownReviewScopes: number; githubReviews: number | null; complete: boolean }
|
|
8
8
|
export interface CostReport { rows: CostRow[]; truncated: boolean }
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* #1087: why a cost scope was refused. The message is fixed text that names the field and its allowed values and never the value given, so a call site may show it
|
|
11
|
+
* as it is. Only this class is passed through; any other error keeps the generic refusal.
|
|
12
|
+
*/
|
|
13
|
+
export class CostScopeError extends Error {
|
|
14
|
+
constructor(message: string) { super(message); this.name = "CostScopeError"; }
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `field` says how the caller's own argument is named, so the refusal points at what the caller actually wrote: `plan` for a task plan (`plan.review.scope`), empty for the
|
|
18
|
+
* plan-review tool's own `review` and `ci` arguments (`review.scope`). One string covers both keys; an object names each key's source separately, for a call that mixes them.
|
|
19
|
+
*/
|
|
20
|
+
export function cleanCostPlan(input: unknown, field: string | { review: string; ci: string } = "plan"): CostPlan {
|
|
21
|
+
const at = (name: string) => { const prefix = typeof field === "string" ? field : field[name.split(".")[0] as "review" | "ci"]; return prefix ? `${prefix}.${name}` : name; };
|
|
22
|
+
const refuse = (message: string): never => { throw new CostScopeError(message); };
|
|
23
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) refuse(`${typeof field === "string" && field ? field : "the request"} must be an object; review and ci are its cost scope fields`);
|
|
11
24
|
const value = input as Record<string, unknown>;
|
|
12
25
|
const result: CostPlan = {};
|
|
13
26
|
if (value.review !== undefined) {
|
|
14
27
|
const review = value.review as Record<string, unknown>;
|
|
15
|
-
if (!review || typeof review !== "object" || Array.isArray(review)
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
if (!review || typeof review !== "object" || Array.isArray(review)) refuse(`${at("review")} must be an object with scope (none, delta or full) and reviewers (a whole number)`);
|
|
29
|
+
if (Object.keys(review).some(key => !["scope", "reviewers"].includes(key))) refuse(`${at("review")} takes only scope and reviewers`);
|
|
30
|
+
if (!["none", "delta", "full"].includes(review.scope as string)) refuse(`${at("review.scope")} must be none, delta or full`);
|
|
31
|
+
if (!Number.isSafeInteger(review.reviewers)) refuse(`${at("review.reviewers")} must be a whole number: 0 when scope is none, 1 to 8 when scope is delta or full`);
|
|
32
|
+
if (review.scope === "none" && review.reviewers !== 0) refuse(`${at("review.reviewers")} must be 0 when scope is none`);
|
|
33
|
+
if (review.scope !== "none" && ((review.reviewers as number) < 1 || (review.reviewers as number) > 8)) refuse(`${at("review.reviewers")} must be 1 to 8 when scope is delta or full`);
|
|
18
34
|
result.review = { scope: review.scope as ReviewScope["scope"], reviewers: review.reviewers as number };
|
|
19
35
|
}
|
|
20
36
|
if (value.ci !== undefined) {
|
|
21
|
-
if (!["focused", "full", "reuse"].includes(value.ci as string))
|
|
37
|
+
if (!["focused", "full", "reuse"].includes(value.ci as string)) refuse(`${at("ci")} must be focused, full or reuse`);
|
|
22
38
|
result.ci = value.ci as CostPlan["ci"];
|
|
23
39
|
}
|
|
24
40
|
return result;
|
package/src/hub/crash.ts
CHANGED
|
@@ -97,3 +97,23 @@ export function lossNotice(lost: JournalDelivery[], taskTitle: (id: number) => s
|
|
|
97
97
|
"Check your work against them before you go on.",
|
|
98
98
|
].join("\n");
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* One line of the crash-recovery report in `ahub status` (#1082). The report is a list of these, kept for the whole run, and what `ahub status` shows is `openCrashNotices` of it, computed when
|
|
103
|
+
* status is asked, so a line whose condition is gone is gone.
|
|
104
|
+
* - `peer` set: the line asks something of that peer's owner ("run ahub codex again", "reconnects by itself if its client is still running"). It stands while that peer is not attached, and clears
|
|
105
|
+
* when it is, whoever started it.
|
|
106
|
+
* - `inFlight` set: the headline. Its count of deliveries the crash left in needs_review is read live, and the clause disappears at zero.
|
|
107
|
+
* - neither: what recovery found or did ("kimi resumed", "pi not resumed (...)"). It is history, not an open action, and stays.
|
|
108
|
+
*/
|
|
109
|
+
export interface CrashNotice { text: string; peer?: string; inFlight?: true }
|
|
110
|
+
|
|
111
|
+
/** The lines `ahub status` shows now: `attached` are the peers that are not offline, `inFlight` is how many of the crash's own deliveries are still in needs_review. */
|
|
112
|
+
export function openCrashNotices(notices: readonly CrashNotice[], live: { attached: ReadonlySet<string>; inFlight: number }): string[] {
|
|
113
|
+
const lines: string[] = [];
|
|
114
|
+
for (const notice of notices) {
|
|
115
|
+
if (notice.peer !== undefined && live.attached.has(notice.peer)) continue;
|
|
116
|
+
lines.push(notice.inFlight ? `${notice.text}${live.inFlight ? `; ${live.inFlight} deliveries it had in flight are in needs_review (ahub queue list)` : ""}` : notice.text);
|
|
117
|
+
}
|
|
118
|
+
return lines;
|
|
119
|
+
}
|