agent-relay-server 0.101.0 → 0.101.1
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/docs/openapi.json +1 -1
- package/package.json +2 -2
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/quota-band-transition.ts +5 -2
- package/src/routes/_shared.ts +15 -0
- package/src/routes/routing-policy.ts +3 -2
- package/src/routing-policy-store.ts +8 -0
- package/src/spawn-targets.ts +13 -5
package/docs/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Relay API",
|
|
5
|
-
"version": "0.101.
|
|
5
|
+
"version": "0.101.1",
|
|
6
6
|
"description": "Real-time message bus for inter-agent communication. Agent-first: this spec is designed for machine consumption — agents can self-discover the full API surface via GET /api/spec.",
|
|
7
7
|
"license": {
|
|
8
8
|
"name": "MIT",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-server",
|
|
3
|
-
"version": "0.101.
|
|
3
|
+
"version": "0.101.1",
|
|
4
4
|
"description": "Lightweight HTTP message relay for inter-agent communication across machines",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"CONTRIBUTING.md"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"agent-relay-sdk": "0.2.
|
|
37
|
+
"agent-relay-sdk": "0.2.83",
|
|
38
38
|
"ajv": "^8.20.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { emitRelayEvent } from "./events";
|
|
21
21
|
import { routeNotification } from "./notification-router";
|
|
22
22
|
import { getSpawnCapableAgentIds } from "./routing-policy-push";
|
|
23
|
-
import { getProviderBandInfo } from "./spawn-targets";
|
|
23
|
+
import { getProviderBandInfo, windowRecommendedAction } from "./spawn-targets";
|
|
24
24
|
import type { ProviderBandInfo, ProviderBandWindow, QuotaBand } from "./spawn-targets";
|
|
25
25
|
import type { ProviderQuotaAdvisoryInput } from "./db/provider-quotas";
|
|
26
26
|
|
|
@@ -112,7 +112,10 @@ export function processBandTransitions(input: ProviderQuotaAdvisoryInput): void
|
|
|
112
112
|
provider: input.provider,
|
|
113
113
|
window,
|
|
114
114
|
previousBand,
|
|
115
|
-
|
|
115
|
+
// #761 — use the window's own band for the action text, not the provider-level band.
|
|
116
|
+
// e.g. a 5h→prefer transition on a provider whose 7d is at avoid-large must say
|
|
117
|
+
// "Prefer…", not "Avoid large jobs…".
|
|
118
|
+
recommendedAction: windowRecommendedAction(effectiveBand, window.slack),
|
|
116
119
|
now: input.now,
|
|
117
120
|
});
|
|
118
121
|
} else {
|
package/src/routes/_shared.ts
CHANGED
|
@@ -362,6 +362,21 @@ export function auditEvent(input: ActivityEventInput): void {
|
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Derive a human-readable actor label from the request's auth context, for use as
|
|
367
|
+
* `updatedBy` in config writes. Prefixes distinguish the auth type so notifications
|
|
368
|
+
* show "by dashboard:alice" rather than an opaque id.
|
|
369
|
+
*/
|
|
370
|
+
export function resolveActorLabel(req: Request): string | undefined {
|
|
371
|
+
const acting = resolveActingUser(req);
|
|
372
|
+
if (acting) return `dashboard:${acting.userId}`;
|
|
373
|
+
const component = getComponentAuth(req);
|
|
374
|
+
if (component) return `agent:${component.sub}`;
|
|
375
|
+
const integration = getIntegrationAuth(req);
|
|
376
|
+
if (integration) return `integration:${integration.name}`;
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
365
380
|
export function memoryContext(req: Request): MemoryBrokerContext {
|
|
366
381
|
const component = getComponentAuth(req);
|
|
367
382
|
if (component) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// GET: agent:read (coordinators read the policy to apply rules).
|
|
3
3
|
// PUT: settings:write:routing (admin writes).
|
|
4
4
|
import { ValidationError } from "../db";
|
|
5
|
-
import { error, json, parseBody, type Handler } from "./_shared";
|
|
5
|
+
import { error, json, parseBody, resolveActorLabel, type Handler } from "./_shared";
|
|
6
6
|
import { getRoutingPolicy, setRoutingPolicy } from "../routing-policy-store";
|
|
7
7
|
import { isRecord } from "agent-relay-sdk";
|
|
8
8
|
import { cleanString } from "../validation";
|
|
@@ -15,7 +15,8 @@ export const putRoutingPolicyRoute: Handler = async (req) => {
|
|
|
15
15
|
const parsed = await parseBody<unknown>(req);
|
|
16
16
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
18
|
+
const bodyUpdatedBy = isRecord(parsed.body) ? cleanString(parsed.body.updatedBy, "updatedBy", { max: 200 }) : undefined;
|
|
19
|
+
const updatedBy = bodyUpdatedBy ?? resolveActorLabel(req);
|
|
19
20
|
const policy = setRoutingPolicy(parsed.body, { updatedBy });
|
|
20
21
|
return json(policy);
|
|
21
22
|
} catch (e) {
|
|
@@ -31,9 +31,17 @@ export function getRoutingPolicy(): RoutingPolicy {
|
|
|
31
31
|
* Persist the routing policy. The incoming value is normalized so partial bodies
|
|
32
32
|
* still produce a valid document. Emits `config.changed` via `setConfig` so the
|
|
33
33
|
* dashboard + any live listeners pick it up immediately.
|
|
34
|
+
*
|
|
35
|
+
* No-op detection: if the normalized incoming policy is deep-equal to the current
|
|
36
|
+
* persisted one, the write and the fleet notification are both skipped so identical
|
|
37
|
+
* saves are fully silent.
|
|
34
38
|
*/
|
|
35
39
|
export function setRoutingPolicy(value: unknown, opts: { updatedBy?: string } = {}): RoutingPolicy {
|
|
36
40
|
const normalized = normalizeRoutingPolicy(value);
|
|
41
|
+
const current = getRoutingPolicy();
|
|
42
|
+
if (JSON.stringify(normalized) === JSON.stringify(current)) {
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
37
45
|
setConfig(ROUTING_POLICY_NAMESPACE, ROUTING_POLICY_KEY, normalized, opts.updatedBy);
|
|
38
46
|
notifyRoutingPolicyChanged(normalized, opts);
|
|
39
47
|
return normalized;
|
package/src/spawn-targets.ts
CHANGED
|
@@ -356,12 +356,20 @@ function recommendedAction(input: { quotaPenaltyBand: SpawnQuotaBurnBand; blocke
|
|
|
356
356
|
? "Quota is blocked now; switch provider or escalate unless this provider was explicitly requested."
|
|
357
357
|
: "5h quota is blocked now; wait for reset or use another provider unless the job is urgent.";
|
|
358
358
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
return windowRecommendedAction(input.quotaPenaltyBand, input.slack);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Per-window recommended action text for a given band — used by band-transition notifications
|
|
364
|
+
* so each window's notification reflects ITS own band, not the provider-level band.
|
|
365
|
+
*/
|
|
366
|
+
export function windowRecommendedAction(band: QuotaBand, slack: number | null): string {
|
|
367
|
+
if (band === "hard-avoid") return "Hard avoid unless explicitly requested or no healthier alternate exists.";
|
|
368
|
+
if (band === "avoid-large") return "Avoid large jobs if a healthier alternate exists.";
|
|
369
|
+
if (band === "caution") return "Caution: prefer cheaper effort/model when practical.";
|
|
362
370
|
// #760 — surface the underburn signal so coordinators load-shift big/deferrable jobs here.
|
|
363
|
-
if (
|
|
364
|
-
const headroom =
|
|
371
|
+
if (band === "prefer") {
|
|
372
|
+
const headroom = slack !== null ? ` (~${Math.round(slack * 100)}% quota would otherwise go unused at reset)` : "";
|
|
365
373
|
return `Prefer for large or deferrable jobs${headroom}.`;
|
|
366
374
|
}
|
|
367
375
|
return "Spawn normally.";
|