@staix/agent-hub 0.12.36 → 0.12.38

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +18 -11
  3. package/docs/agent-notes/adapters.md +19 -6
  4. package/docs/agent-notes/budget.md +2 -1
  5. package/docs/agent-notes/bus.md +3 -2
  6. package/docs/agent-notes/daemon.md +9 -3
  7. package/docs/agent-notes/tasks.md +8 -4
  8. package/docs/agent-notes/tests.md +2 -2
  9. package/docs/memory.md +3 -1
  10. package/docs/operations.md +57 -10
  11. package/docs/quickstart.md +15 -7
  12. package/docs/sdlc.md +29 -3
  13. package/docs/security.md +10 -2
  14. package/docs/smoke.md +38 -2
  15. package/docs/specs/2026-09-19-agent-hub-design.md +58 -4
  16. package/docs/verification/2026-10-19-0.12.37.md +60 -0
  17. package/docs/verification/2026-10-19-agy-p1-t0.md +55 -0
  18. package/docs/verification/2026-10-19-bus-defer-reset.md +9 -0
  19. package/docs/verification/2026-10-19-jcode-0.98.1.md +30 -0
  20. package/docs/verification/2026-10-19-kimi-agy-reviewer-isolation.md +220 -0
  21. package/docs/verification/2026-10-19-reviewer-isolation.md +119 -0
  22. package/docs/verification/2026-10-20-0.12.38.md +56 -0
  23. package/docs/verified.json +27 -15
  24. package/package.json +2 -2
  25. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  26. package/plugins/agent-hub/server.js +5627 -3670
  27. package/src/adapters/acp.ts +3 -2
  28. package/src/adapters/agy/definition.ts +61 -0
  29. package/src/adapters/agy/entry.ts +76 -0
  30. package/src/adapters/agy/hooks.ts +81 -0
  31. package/src/adapters/claude-channel.ts +1 -0
  32. package/src/adapters/claude-print.ts +18 -156
  33. package/src/adapters/codex-review-isolation.ts +290 -0
  34. package/src/adapters/definitions/builtin-metadata.ts +9 -6
  35. package/src/adapters/definitions/native-cli-entry-claude.ts +3 -0
  36. package/src/adapters/definitions/native-cli-entry-codex.ts +5 -1
  37. package/src/adapters/definitions/native-cli-entry-jcode.ts +2 -0
  38. package/src/adapters/definitions/native-cli-entry-kimi.ts +12 -4
  39. package/src/adapters/definitions/native-cli-entry-local.ts +3 -0
  40. package/src/adapters/definitions/native-cli-entry-pi.ts +7 -1
  41. package/src/adapters/definitions/native-cli-entry-qwen.ts +3 -0
  42. package/src/adapters/definitions/native-cli-entry.ts +2 -0
  43. package/src/adapters/definitions/native-cli-retained.ts +15 -0
  44. package/src/adapters/definitions/native-factories.ts +17 -0
  45. package/src/adapters/definitions/native-policy-aux.ts +1 -1
  46. package/src/adapters/definitions/qwen.ts +1 -1
  47. package/src/adapters/definitions/reviewer-variants.ts +33 -0
  48. package/src/adapters/jcode.ts +20 -5
  49. package/src/adapters/kimi-review.ts +161 -0
  50. package/src/adapters/registry-types.ts +36 -4
  51. package/src/adapters/registry.ts +28 -7
  52. package/src/adapters/reviewer-mcp.ts +85 -0
  53. package/src/adapters/reviewer-print.ts +382 -0
  54. package/src/cli/agy-mcp.ts +370 -0
  55. package/src/cli/console-state.ts +6 -0
  56. package/src/cli/console.ts +2 -0
  57. package/src/cli/facts-hook.ts +15 -2
  58. package/src/cli/help.ts +2 -0
  59. package/src/cli/identity.ts +1 -1
  60. package/src/cli/init.ts +7 -0
  61. package/src/cli/launch.ts +3 -1
  62. package/src/cli/main.ts +20 -0
  63. package/src/cli/memory.ts +13 -12
  64. package/src/cli/output.ts +5 -0
  65. package/src/hub/board.ts +34 -6
  66. package/src/hub/bus.ts +17 -6
  67. package/src/hub/config-apply.ts +3 -2
  68. package/src/hub/control-client.ts +7 -1
  69. package/src/hub/daemon.ts +266 -46
  70. package/src/hub/envelope.ts +1 -1
  71. package/src/hub/host-executable.ts +1 -1
  72. package/src/hub/hub-tools.ts +5 -0
  73. package/src/hub/instances.ts +13 -3
  74. package/src/hub/memory-profile.ts +67 -11
  75. package/src/hub/native-hook-launch.ts +37 -0
  76. package/src/hub/peer-catalog.ts +2 -3
  77. package/src/hub/planning.ts +2 -0
  78. package/src/hub/release-admission.ts +64 -0
  79. package/src/hub/reviewer-pool-config.ts +11 -0
  80. package/src/hub/reviewer-pool.ts +76 -0
  81. package/src/hub/routing.ts +45 -3
  82. package/src/hub/sdlc-release.ts +113 -0
  83. package/src/hub/tasks.ts +261 -36
  84. package/src/ui/index.html +6 -1
  85. package/templates/AGENTS.block.md +1 -1
@@ -1,3 +1,5 @@
1
+ import type { Task } from "./board.ts";
2
+ import type { Release } from "./planning.ts";
1
3
  import { spawn } from "node:child_process";
2
4
  import { fileURLToPath } from "node:url";
3
5
  import { childEnv, stopOwnedProcess } from "./child-process.ts";
@@ -123,3 +125,114 @@ export async function readReleaseReadiness(root: string, repo: string, liveIssue
123
125
  report.blockers.push("native acceptance and person release decision unverified");
124
126
  return report;
125
127
  }
128
+
129
+ export type HotfixCause = "loss" | "security_privacy" | "upgrade_start_blocked" | "core_flow_no_workaround";
130
+ export interface ReleaseTrainData {
131
+ hotfix?: { cause: HotfixCause; reason: string };
132
+ scope?: number[];
133
+ history: { at: number; by: string; event: "configured" | "cut" | "shipped"; scope?: number[]; ciRuns?: number; reviews?: number }[];
134
+ }
135
+ export const RELEASE_TRAIN_HISTORY_LIMIT = 256;
136
+ const HOTFIX_CAUSES: readonly string[] = ["loss", "security_privacy", "upgrade_start_blocked", "core_flow_no_workaround"];
137
+ const trainObject = (value: unknown): Record<string, unknown> => {
138
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("release train: expected an object");
139
+ return value as Record<string, unknown>;
140
+ };
141
+ const trainKeys = (value: Record<string, unknown>, keys: readonly string[]) => {
142
+ if (Object.keys(value).some(key => !keys.includes(key))) throw new Error("release train: unknown field");
143
+ };
144
+ const trainScope = (value: unknown): number[] => {
145
+ // ponytail: 1000 tasks per release; raise together with bounded report readers if larger trains are needed.
146
+ if (!Array.isArray(value) || value.length > 1000 || value.some(id => !Number.isSafeInteger(id) || id < 1) || new Set(value).size !== value.length) throw new Error("release train: invalid scope");
147
+ return [...value].sort((a, b) => a - b);
148
+ };
149
+
150
+ /** Structural validation only: the Tasks boundary screens reason text and establishes caller authority. */
151
+ export function releaseTrainData(input: unknown): ReleaseTrainData {
152
+ const raw = trainObject(input); trainKeys(raw, ["hotfix", "scope", "history"]);
153
+ const result: ReleaseTrainData = { history: [] };
154
+ if (raw.hotfix !== undefined) {
155
+ const hotfix = trainObject(raw.hotfix); trainKeys(hotfix, ["cause", "reason"]);
156
+ if (typeof hotfix.cause !== "string" || !HOTFIX_CAUSES.includes(hotfix.cause) || typeof hotfix.reason !== "string" || !hotfix.reason.trim() || Buffer.byteLength(hotfix.reason) > 1000) throw new Error("release train: invalid hotfix cause or reason");
157
+ result.hotfix = { cause: hotfix.cause as HotfixCause, reason: hotfix.reason.trim() };
158
+ }
159
+ if (raw.scope !== undefined) result.scope = trainScope(raw.scope);
160
+ // ponytail: 256 recorded train events; archive settled release evidence before increasing this ceiling.
161
+ if (!Array.isArray(raw.history) || raw.history.length > RELEASE_TRAIN_HISTORY_LIMIT) throw new Error("release train: invalid history");
162
+ result.history = raw.history.map(value => {
163
+ const row = trainObject(value); trainKeys(row, ["at", "by", "event", "scope", "ciRuns", "reviews"]);
164
+ if (!Number.isSafeInteger(row.at) || (row.at as number) < 1 || (row.at as number) > 8_640_000_000_000_000 || typeof row.by !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(row.by) || !["configured", "cut", "shipped"].includes(String(row.event))) throw new Error("release train: invalid history event");
165
+ const event: ReleaseTrainData["history"][number] = { at: row.at as number, by: row.by, event: row.event as "configured" | "cut" | "shipped" };
166
+ if (row.scope !== undefined) event.scope = trainScope(row.scope);
167
+ for (const key of ["ciRuns", "reviews"] as const) if (row[key] !== undefined) {
168
+ if (!Number.isSafeInteger(row[key]) || (row[key] as number) < 0 || (row[key] as number) > 1_000_000) throw new Error("release train: invalid resource count");
169
+ event[key] = row[key] as number;
170
+ }
171
+ return event;
172
+ });
173
+ if (result.history.some((row, index) => index > 0 && row.at < result.history[index - 1]!.at)) throw new Error("release train: history dates are out of order");
174
+ return result;
175
+ }
176
+
177
+ const storedTrain = (release: Release): ReleaseTrainData | undefined => {
178
+ const raw = (release as Release & { train?: ReleaseTrainData }).train;
179
+ if (raw === undefined) return undefined;
180
+ try { return releaseTrainData(raw); } catch { return undefined; }
181
+ };
182
+
183
+ /** Board metadata only; a trigger is a request to cut, never release authorization or native readiness. */
184
+ export function releaseTrainReadiness(release: Release, tasks: Task[], now = Date.now()) {
185
+ const train = storedTrain(release), assigned = tasks.filter(task => task.release === release.id), frozen = ["frozen", "shipped"].includes(release.state);
186
+ const scope = frozen ? train?.scope : assigned.map(task => task.id);
187
+ const blockers: string[] = [];
188
+ if ((release as Release & { train?: ReleaseTrainData }).train !== undefined && !train) blockers.push("release train evidence unknown");
189
+ if (!scope) blockers.push("release scope unknown");
190
+ else if (!scope.length) blockers.push("release scope empty");
191
+ if (frozen && scope && JSON.stringify([...scope].sort((a, b) => a - b)) !== JSON.stringify(assigned.map(task => task.id).sort((a, b) => a - b))) blockers.push("frozen release scope changed");
192
+ const rows = new Map(tasks.map(task => [task.id, task]));
193
+ const states = (scope ?? []).map(id => {
194
+ const task = rows.get(id), state = task?.state ?? "unknown", ready = state === "approved";
195
+ if (!task) blockers.push(`task #${id}: scope evidence unknown`);
196
+ else if (task.release !== release.id) blockers.push(`task #${id}: release assignment changed`);
197
+ if (!ready) blockers.push(`task #${id}: not approved`);
198
+ return { id, state, ready };
199
+ });
200
+ const trigger: "tasks_approved" | "planned_date" | "waiting" = states.length && states.every(task => task.ready) && !blockers.length ? "tasks_approved"
201
+ : release.cut !== null && Number.isFinite(now) && release.cut <= now ? "planned_date" : "waiting";
202
+ return { trigger, tasks: states, blockers };
203
+ }
204
+
205
+ /** Counts are explicit recorded event deltas, not inferred from successful gates or absent history. */
206
+ export function releaseMetrics(releases: Release[], tasks: Task[]) {
207
+ const unknown = new Set<number>(), shipped: { id: number; at: number }[] = [];
208
+ const approvalToReleaseMs: { release: number; task: number; ms: number | null }[] = [];
209
+ let hotfixCount = 0, knownKinds = 0, ciRuns = 0, reviews = 0, ciKnown = true, reviewsKnown = true;
210
+ const byTask = new Map(tasks.map(task => [task.id, task]));
211
+ const settled = releases.filter(release => release.state === "shipped");
212
+ for (const release of settled) {
213
+ const train = storedTrain(release);
214
+ if (!train?.history.length) { unknown.add(release.id); ciKnown = false; reviewsKnown = false; continue; }
215
+ knownKinds++;
216
+ if (train.hotfix) hotfixCount++;
217
+ for (const key of ["ciRuns", "reviews"] as const) {
218
+ const recorded = train.history.flatMap(event => event[key] === undefined ? [] : [event[key]!]);
219
+ if (!recorded.length) { unknown.add(release.id); if (key === "ciRuns") ciKnown = false; else reviewsKnown = false; }
220
+ else if (key === "ciRuns") ciRuns += recorded.reduce((sum, value) => sum + value, 0);
221
+ else reviews += recorded.reduce((sum, value) => sum + value, 0);
222
+ }
223
+ const event = [...train.history].reverse().find(row => row.event === "shipped");
224
+ if (!event || release.shippedAt !== event.at) { unknown.add(release.id); continue; }
225
+ shipped.push({ id: release.id, at: event.at });
226
+ const scope = train.scope ?? [...train.history].reverse().find(row => row.event === "cut")?.scope;
227
+ if (!scope) { unknown.add(release.id); continue; }
228
+ for (const id of scope) {
229
+ const task = byTask.get(id), approved = task?.history.findLast(row => row.at <= event.at && (row.event === "approved" || row.event === "done" && task.state === "approved" && task.reviewer === null))?.at ?? null;
230
+ const ms = approved !== null && approved <= event.at ? event.at - approved : null;
231
+ if (ms === null) unknown.add(release.id);
232
+ approvalToReleaseMs.push({ release: release.id, task: id, ms });
233
+ }
234
+ }
235
+ shipped.sort((a, b) => a.at - b.at || a.id - b.id);
236
+ const intervals = shipped.length !== releases.filter(release => release.state === "shipped").length ? [] : shipped.slice(1).map((row, index) => ({ from: shipped[index]!.id, to: row.id, ms: row.at - shipped[index]!.at }));
237
+ return { intervals, approvalToReleaseMs, hotfixCount: knownKinds === settled.length && knownKinds > 0 ? hotfixCount : null, hotfixRate: knownKinds === settled.length && knownKinds > 0 ? hotfixCount / knownKinds : null, ciRuns: ciKnown && settled.length ? ciRuns : null, reviews: reviewsKnown && settled.length ? reviews : null, unknown: [...unknown].sort((a, b) => a - b) };
238
+ }