@staix/agent-hub 0.12.16 → 0.12.18
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 +14 -0
- package/README.md +4 -4
- package/docs/agent-notes/adapters.md +1 -0
- package/docs/agent-notes/bus.md +2 -0
- package/docs/agent-notes/tasks.md +3 -1
- package/docs/agent-notes/tests.md +1 -0
- package/docs/events.md +37 -3
- package/docs/operations.md +149 -10
- package/docs/quickstart.md +3 -3
- package/docs/security.md +28 -1
- package/docs/smoke.md +93 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +176 -1
- package/docs/verification/2026-10-09-agent-shell-t0.md +123 -0
- package/docs/verified.json +26 -17
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +17 -5
- package/src/adapters/acp.ts +2 -2
- package/src/adapters/claude-channel.ts +3 -2
- package/src/adapters/codex-appserver.ts +3 -2
- package/src/adapters/local-worker.ts +16 -12
- package/src/cli/console-state.ts +279 -0
- package/src/cli/console.ts +224 -0
- package/src/cli/facts-hook.ts +8 -1
- package/src/cli/identity-audit.ts +66 -0
- package/src/cli/identity.ts +54 -0
- package/src/cli/launch.ts +15 -2
- package/src/cli/main.ts +75 -32
- package/src/cli/tail-render.ts +17 -0
- package/src/cli/upgrade-runtime.ts +1 -1
- package/src/hub/attribution.ts +20 -0
- package/src/hub/board.ts +6 -2
- package/src/hub/bus.ts +88 -10
- package/src/hub/child-process.ts +11 -0
- package/src/hub/conductor.ts +196 -0
- package/src/hub/control-client.ts +3 -3
- package/src/hub/daemon.ts +388 -51
- package/src/hub/envelope.ts +1 -1
- package/src/hub/events.ts +10 -4
- package/src/hub/hub-tools.ts +13 -0
- package/src/hub/report.ts +162 -4
- package/src/hub/supervision.ts +152 -0
- package/src/hub/tasks.ts +30 -15
- package/src/hub/usage.ts +37 -2
- package/src/pi/launch.ts +2 -1
package/src/hub/usage.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { normalizeUsage, safeModelLabel, type NormalizedUsage } from "../omnirou
|
|
|
4
4
|
|
|
5
5
|
export interface UsageRecord {
|
|
6
6
|
id: string;
|
|
7
|
+
/** True only for a native assistant end_turn row, never a tool-use/compaction message. */
|
|
8
|
+
completedTurn?: boolean;
|
|
7
9
|
at?: string;
|
|
8
10
|
usage?: NormalizedUsage;
|
|
9
11
|
requestedModel?: string;
|
|
@@ -11,6 +13,39 @@ export interface UsageRecord {
|
|
|
11
13
|
provider?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
/** Claude-native counters have separate input/output/cache categories, often no explicit total. */
|
|
17
|
+
export function claudeReportedTokens(usage: NormalizedUsage | undefined): number | undefined {
|
|
18
|
+
if (!usage) return undefined;
|
|
19
|
+
if (usage.totalTokens !== undefined) return usage.totalTokens;
|
|
20
|
+
if (usage.inputTokens === undefined || usage.outputTokens === undefined) return undefined;
|
|
21
|
+
const total = usage.inputTokens + usage.outputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
22
|
+
return Number.isSafeInteger(total) && total >= 0 ? total : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ClaudeCompletionWait = { record?: UsageRecord; reason: "verified" | "unavailable" | "baseline" | "before-start" | "superseded" };
|
|
26
|
+
/** After acknowledging Stop, observe its delayed transcript append within a bounded 1200 ms window. */
|
|
27
|
+
export async function waitForClaudeCompletion(read: () => UsageRecord | undefined, current: () => boolean, start: { at: number; baseline?: string }, timeoutMs = 1200): Promise<ClaudeCompletionWait> {
|
|
28
|
+
const budget = Number.isFinite(timeoutMs) ? Math.min(1200, Math.max(0, timeoutMs)) : 1200;
|
|
29
|
+
if (budget === 0) return { reason: "unavailable" };
|
|
30
|
+
const deadline = performance.now() + budget;
|
|
31
|
+
let reason: ClaudeCompletionWait["reason"] = "unavailable";
|
|
32
|
+
while (true) {
|
|
33
|
+
if (!current()) return { reason: "superseded" };
|
|
34
|
+
const record = read();
|
|
35
|
+
if (!current()) return { reason: "superseded" };
|
|
36
|
+
if (performance.now() > deadline) return { reason };
|
|
37
|
+
const at = typeof record?.at === "string" ? Date.parse(record.at) : NaN;
|
|
38
|
+
if (record?.completedTurn && Number.isFinite(at)) {
|
|
39
|
+
if (record.id === start.baseline) reason = "baseline";
|
|
40
|
+
else if (at < start.at) reason = "before-start";
|
|
41
|
+
else return { record, reason: "verified" };
|
|
42
|
+
} else reason = "unavailable";
|
|
43
|
+
const left = deadline - performance.now();
|
|
44
|
+
if (left <= 0) return { reason };
|
|
45
|
+
await Bun.sleep(Math.min(40, left));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
14
49
|
function opaqueId(sessionId: string, messageId: string): string {
|
|
15
50
|
return createHash("sha256").update(sessionId).update("\0").update(messageId).digest("hex");
|
|
16
51
|
}
|
|
@@ -30,7 +65,7 @@ export function readClaudeTranscriptUsage(sessionId: string, transcriptPath: str
|
|
|
30
65
|
// Signal unavailable coverage with a stable opaque id; a repeated poll dedupes in the report.
|
|
31
66
|
return [{ id: opaqueId(sessionId, "transcript-too-large") }];
|
|
32
67
|
}
|
|
33
|
-
const latest = new Map<string, { usage?: NormalizedUsage; at?: string; servedModel?: string }>();
|
|
68
|
+
const latest = new Map<string, { usage?: NormalizedUsage; at?: string; servedModel?: string; completedTurn?: boolean }>();
|
|
34
69
|
for (const line of readFileSync(transcriptPath, "utf8").split("\n")) {
|
|
35
70
|
if (!line.trim()) continue;
|
|
36
71
|
try {
|
|
@@ -43,7 +78,7 @@ export function readClaudeTranscriptUsage(sessionId: string, transcriptPath: str
|
|
|
43
78
|
const usage = supportedStopReason ? normalizeUsage(message.usage) : undefined;
|
|
44
79
|
const at = typeof row.timestamp === "string" && Number.isFinite(Date.parse(row.timestamp)) ? new Date(row.timestamp).toISOString() : undefined;
|
|
45
80
|
const servedModel = safeModelLabel(message.model);
|
|
46
|
-
latest.set(message.id, { ...(usage ? { usage } : {}), ...(at ? { at } : {}), ...(servedModel ? { servedModel } : {}) });
|
|
81
|
+
latest.set(message.id, { ...(usage ? { usage } : {}), ...(at ? { at } : {}), ...(servedModel ? { servedModel } : {}), ...(message.stop_reason === "end_turn" ? { completedTurn: true } : {}) });
|
|
47
82
|
} catch {
|
|
48
83
|
// A streaming or crash-truncated line is not a record.
|
|
49
84
|
}
|
package/src/pi/launch.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { peerChildEnv } from "../hub/child-process.ts";
|
|
2
3
|
import type { PiOptions, PiModelDescriptor, PiTuiLaunch } from "../adapters/pi.ts";
|
|
3
4
|
|
|
4
5
|
const modelFor = (backend: PiOptions["backend"], models: PiModelDescriptor[]): string => {
|
|
@@ -12,7 +13,7 @@ const modelFor = (backend: PiOptions["backend"], models: PiModelDescriptor[]): s
|
|
|
12
13
|
|
|
13
14
|
/** Pure native launch; bridge/relay setup and session validation remain with the adapter. */
|
|
14
15
|
export function buildPiLaunch(opts: Pick<PiOptions, "stateDir" | "cmd" | "mode" | "backend" | "model" | "relay" | "tools" | "preamble" | "maxSteps" | "sessionFile" | "sessionId">, inherited: NodeJS.ProcessEnv, extension: string, bridgePort: number | string, bridgeToken: string): PiTuiLaunch {
|
|
15
|
-
const env: NodeJS.ProcessEnv = { ...inherited, PI_CODING_AGENT_DIR: join(opts.stateDir, "pi"), AGENTHUB_PI_BRIDGE_URL: `http://127.0.0.1:${bridgePort}`, AGENTHUB_PI_BRIDGE_TOKEN: bridgeToken, AGENTHUB_PI_OWNER_TOKEN: bridgeToken, AGENTHUB_PI_RELAY_URL: opts.relay.url, AGENTHUB_PI_RELAY_TOKEN: opts.relay.token, AGENTHUB_PI_MODELS: JSON.stringify(opts.relay.models), AGENTHUB_PI_TOOLS: JSON.stringify(opts.tools), AGENTHUB_PI_MAX_STEPS: String(opts.maxSteps ?? 30) };
|
|
16
|
+
const env: NodeJS.ProcessEnv = { ...peerChildEnv("pi", inherited), PI_CODING_AGENT_DIR: join(opts.stateDir, "pi"), AGENTHUB_PI_BRIDGE_URL: `http://127.0.0.1:${bridgePort}`, AGENTHUB_PI_BRIDGE_TOKEN: bridgeToken, AGENTHUB_PI_OWNER_TOKEN: bridgeToken, AGENTHUB_PI_RELAY_URL: opts.relay.url, AGENTHUB_PI_RELAY_TOKEN: opts.relay.token, AGENTHUB_PI_MODELS: JSON.stringify(opts.relay.models), AGENTHUB_PI_TOOLS: JSON.stringify(opts.tools), AGENTHUB_PI_MAX_STEPS: String(opts.maxSteps ?? 30) };
|
|
16
17
|
const args = [ ...(opts.mode === "headless" ? ["--mode", "rpc"] : []), "--provider", "agent-hub-local", "--model", opts.model ?? modelFor(opts.backend, opts.relay.models), "--models", opts.relay.models.map((model) => `agent-hub-local/${model.id}`).join(","), "--no-builtin-tools", "--no-skills", "--no-prompt-templates", "--no-extensions", "--extension", extension, ...(opts.preamble ? ["--append-system-prompt", opts.preamble] : []), "--session-dir", join(opts.stateDir, "pi-sessions")];
|
|
17
18
|
if (opts.sessionFile) args.push("--session", opts.sessionFile); else if (opts.sessionId) args.push("--session-id", opts.sessionId);
|
|
18
19
|
const command = opts.cmd ?? ["pi"];
|