@staix/agent-hub 0.12.7 → 0.12.9

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 (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSES/Apache-2.0.txt +204 -0
  3. package/THIRD_PARTY_NOTICES.md +19 -0
  4. package/docs/cooperbench.md +30 -2
  5. package/docs/events.md +32 -0
  6. package/docs/operations.md +26 -4
  7. package/docs/specs/2026-09-19-agent-hub-design.md +10 -0
  8. package/docs/specs/2026-09-20-pi-local-models-design.md +1 -1
  9. package/docs/specs/2026-10-04-switchyard-source-port-design.md +309 -0
  10. package/package.json +4 -2
  11. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  12. package/plugins/agent-hub/server.js +4 -2
  13. package/src/adapters/acp.ts +26 -9
  14. package/src/adapters/codex-appserver.ts +2 -2
  15. package/src/adapters/local-worker.ts +138 -26
  16. package/src/adapters/pi.ts +8 -0
  17. package/src/hub/daemon.ts +59 -17
  18. package/src/hub/events.ts +5 -0
  19. package/src/hub/inference.ts +20 -0
  20. package/src/hub/progress.ts +251 -0
  21. package/src/hub/routing.ts +4 -0
  22. package/src/local/tools.ts +9 -0
  23. package/src/models/relay.ts +136 -22
  24. package/src/models/route/advisor.ts +102 -0
  25. package/src/models/route/config.ts +41 -0
  26. package/src/models/route/escalation.ts +82 -0
  27. package/src/models/route/judge.ts +23 -0
  28. package/src/models/route/labels.ts +167 -0
  29. package/src/models/route/normalize.ts +103 -0
  30. package/src/models/route/plan-execute.ts +52 -0
  31. package/src/models/route/prompts.ts +8 -0
  32. package/src/models/route/relay-selector.ts +102 -0
  33. package/src/models/route/runtime.ts +156 -0
  34. package/src/models/route/signals.ts +234 -0
  35. package/src/models/route/stage.ts +93 -0
  36. package/src/models/route/state.ts +54 -0
  37. package/src/models/route/text.ts +61 -0
  38. package/src/omniroute/client.ts +8 -1
  39. package/templates/routing.toml +28 -0
@@ -0,0 +1,61 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Ported to TypeScript from NVIDIA NeMo Switchyard crates/libsy/src/algorithms/advisor_gate/transcript.rs and algorithms/util/{escalation,prompts,llm_judge}.rs at commit c8848511, modified.
4
+
5
+ import type { ChatMessage } from "../../omniroute/client.ts";
6
+
7
+ /** Unicode code-point aware middle truncation used by Switchyard's advisor. */
8
+ export function middleDrop(text: string, cap: number): string {
9
+ const chars = Array.from(text);
10
+ if (chars.length <= cap) return text;
11
+ const headCount = Math.max(0, Math.floor(cap / 4));
12
+ const tailCount = Math.max(0, cap - headCount);
13
+ return `${chars.slice(0, headCount).join("")}\n...<middle of the conversation truncated>...\n${chars.slice(-tailCount || chars.length).join("")}`;
14
+ }
15
+
16
+ /** Bounded head/tail truncation for escalation summaries, measured in code points. */
17
+ export function truncateCodepoints(text: string, limit: number): string {
18
+ const chars = Array.from(text);
19
+ if (chars.length <= limit) return text;
20
+ const marker = " ...[trimmed] ";
21
+ const keep = Math.min(chars.length, Math.max(20, limit - Array.from(marker).length));
22
+ const head = Math.floor(keep * 2 / 3);
23
+ const tail = keep - head;
24
+ return `${chars.slice(0, head).join("")}${marker}${chars.slice(-tail).join("")}`;
25
+ }
26
+
27
+ const verdictPattern = /^[\s*_#>"'(\[`]*(?:(?:final\s+)?verdict\s*:\s*[\s*_#>"'(\[`]*)?(APPROVE|REDO)(?![\p{Alphabetic}\p{M}\p{Nd}\p{Pc}\p{Join_Control}])/iu;
28
+
29
+ export type AdvisorVerdict = { kind: "approve" } | { kind: "redo"; plan: string };
30
+
31
+ /** Only an anchored first-word verdict is trusted. */
32
+ export function parseAdvisorVerdict(reply: string): AdvisorVerdict | undefined {
33
+ const trimmed = reply.trim();
34
+ const match = verdictPattern.exec(trimmed);
35
+ if (!match) return undefined;
36
+ if (match[1]?.toUpperCase() === "APPROVE") return { kind: "approve" };
37
+ const tail = trimmed.slice(match[0].length).trimStart().replace(/^[ *_:\n-]+/u, "").trim();
38
+ return { kind: "redo", plan: tail || trimmed };
39
+ }
40
+
41
+ /** Removes only the JSON fences supported by Switchyard's typed judge parser. */
42
+ export function stripJsonFence(text: string): string {
43
+ let value = text.trim();
44
+ if (!value.startsWith("```")) return value;
45
+ value = value.slice(3);
46
+ if (value.startsWith("json")) value = value.slice(4);
47
+ value = value.replace(/^[\n\r]+/u, "");
48
+ return value.endsWith("```") ? value.slice(0, -3).trim() : value;
49
+ }
50
+
51
+ /** Add a routing note without creating consecutive user turns. */
52
+ export function appendNote(messages: readonly ChatMessage[], note: string): ChatMessage[] {
53
+ const output = messages.map(message => ({ ...message }));
54
+ const last = output.at(-1);
55
+ if (last?.role === "user") {
56
+ last.content = last.content ? `${last.content}\n${note}` : note;
57
+ } else {
58
+ output.push({ role: "user", content: note });
59
+ }
60
+ return output;
61
+ }
@@ -24,8 +24,12 @@ export interface ChatMessage {
24
24
  content: string | null;
25
25
  tool_calls?: ToolCall[];
26
26
  tool_call_id?: string;
27
+ /** Internal local-tool outcome, consumed by route normalization and omitted from provider transport. */
28
+ is_error?: boolean;
27
29
  }
28
30
  export interface ChatResult {
31
+ /** Internal routing telemetry id, never part of a provider payload. */
32
+ routeDecision?: string;
29
33
  message: ChatMessage;
30
34
  /** Usage counters returned by the provider, when valid. No missing counter is inferred as zero. */
31
35
  usage?: NormalizedUsage;
@@ -37,6 +41,8 @@ export interface ChatResult {
37
41
  selectedModel?: string;
38
42
  }
39
43
  export interface ChatOptions {
44
+ /** Refuse a changed/off-campus gateway immediately before transport. */
45
+ onCampusOnly?: boolean;
40
46
  signal?: AbortSignal;
41
47
  /** Switchyard base (`http://127.0.0.1:<port>/v1`). The sidecar holds the key, so none is sent. */
42
48
  via?: string;
@@ -155,6 +161,7 @@ export class OmniRoute {
155
161
  async chat(body: { model: string; messages: ChatMessage[]; tools?: unknown[]; max_tokens?: number }, opts: ChatOptions = {}): Promise<ChatResult> {
156
162
  const base = opts.via ?? (await this.base());
157
163
  if (!base) throw new Error("no model gateway is configured or reachable (omniroute.urls in .agenthub/config.json; see ahub doctor)");
164
+ if (opts.onCampusOnly && (opts.via || this.isAccessHost(base))) throw new Error("PII model calls require the confirmed campus gateway");
158
165
  const headers: Record<string, string> = { "content-type": "application/json" };
159
166
  if (opts.via) {
160
167
  if (opts.sessionId) headers["x-switchyard-session-id"] = opts.sessionId;
@@ -168,7 +175,7 @@ export class OmniRoute {
168
175
  res = await fetch(`${base}/chat/completions`, {
169
176
  method: "POST",
170
177
  headers,
171
- body: JSON.stringify({ ...body, stream: false }),
178
+ body: JSON.stringify({ ...body, messages: body.messages.map(({ is_error: _internalError, ...message }) => message), stream: false }),
172
179
  ...(opts.signal ? { signal: opts.signal } : {}),
173
180
  });
174
181
  } catch (e) {
@@ -93,3 +93,31 @@ peers = ["pi", "local", "kimi"]
93
93
  route = "sy/fast"
94
94
  pi_backend = "mlx"
95
95
  escalate_to = ["codex", "kimi"]
96
+
97
+ # In-process L2 routes are opt-in: set local.route or a class route to hub/<id>.
98
+ # They never enter the optional Switchyard sidecar TOML. Defaults are efficient fast, capable coding.
99
+ [hub_routes."hub/stage"]
100
+ type = "stage"
101
+ efficient = "fast"
102
+ capable = "coding"
103
+ confidence_threshold = 0.5
104
+ hold_turns = 2
105
+
106
+ [hub_routes."hub/plan"]
107
+ type = "plan_execute"
108
+ efficient = "fast"
109
+ capable = "coding"
110
+
111
+ [hub_routes."hub/advisor"]
112
+ type = "advisor"
113
+ efficient = "fast"
114
+ capable = "coding"
115
+ max_reviews = 1
116
+ trigger = "no_tool_call"
117
+ gate_min_tool_results = 1
118
+
119
+ [hub_routes."hub/escalation"]
120
+ type = "escalation"
121
+ efficient = "fast"
122
+ capable = "coding"
123
+ confirmations = 2