agent-relay-server 0.107.0 → 0.108.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/docs/openapi.json +475 -1
- package/package.json +4 -4
- package/public/assets/{MessageBubble-PEdvVh_m.js → MessageBubble-DYJezSjU.js} +2 -2
- package/public/assets/{MessageBubble-PEdvVh_m.js.map → MessageBubble-DYJezSjU.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-DSts7Rtq.js → PlanGraphPanel-MWcVw38W.js} +2 -2
- package/public/assets/{PlanGraphPanel-DSts7Rtq.js.map → PlanGraphPanel-MWcVw38W.js.map} +1 -1
- package/public/assets/{activity-BcYf8YNO.js → activity-Db3X3dg8.js} +2 -2
- package/public/assets/{activity-BcYf8YNO.js.map → activity-Db3X3dg8.js.map} +1 -1
- package/public/assets/{agent-profiles-CuTQtmV6.js → agent-profiles-Di2w5U1o.js} +2 -2
- package/public/assets/{agent-profiles-CuTQtmV6.js.map → agent-profiles-Di2w5U1o.js.map} +1 -1
- package/public/assets/{agents-CgRFJyks.js → agents-GSJXvmU1.js} +2 -2
- package/public/assets/{agents-CgRFJyks.js.map → agents-GSJXvmU1.js.map} +1 -1
- package/public/assets/{analytics-C3OTFIXo.js → analytics-B9USQLl5.js} +2 -2
- package/public/assets/{analytics-C3OTFIXo.js.map → analytics-B9USQLl5.js.map} +1 -1
- package/public/assets/{automation-CH9YGYoE.js → automation-XRePs1dT.js} +2 -2
- package/public/assets/{automation-CH9YGYoE.js.map → automation-XRePs1dT.js.map} +1 -1
- package/public/assets/{branch-state-badge-CcLZ_u86.js → branch-state-badge-C_VNyER3.js} +2 -2
- package/public/assets/{branch-state-badge-CcLZ_u86.js.map → branch-state-badge-C_VNyER3.js.map} +1 -1
- package/public/assets/{channels-CcSnLzCG.js → channels-DACvNoC2.js} +2 -2
- package/public/assets/{channels-CcSnLzCG.js.map → channels-DACvNoC2.js.map} +1 -1
- package/public/assets/{chat-BLcm2Aht.js → chat-D_0uHgB6.js} +2 -2
- package/public/assets/{chat-BLcm2Aht.js.map → chat-D_0uHgB6.js.map} +1 -1
- package/public/assets/{connectors-DbPOlQhL.js → connectors-C0B38LUK.js} +2 -2
- package/public/assets/{connectors-DbPOlQhL.js.map → connectors-C0B38LUK.js.map} +1 -1
- package/public/assets/display-1J__Ti1K.js +3 -0
- package/public/assets/display-1J__Ti1K.js.map +1 -0
- package/public/assets/{formatted-body-impl-BJTthVHg.js → formatted-body-impl-BKWCAjBd.js} +2 -2
- package/public/assets/{formatted-body-impl-BJTthVHg.js.map → formatted-body-impl-BKWCAjBd.js.map} +1 -1
- package/public/assets/index-DlBSuzd2.js +23 -0
- package/public/assets/index-DlBSuzd2.js.map +1 -0
- package/public/assets/{insights-DklrIhb6.js → insights-BzNoYvDe.js} +2 -2
- package/public/assets/{insights-DklrIhb6.js.map → insights-BzNoYvDe.js.map} +1 -1
- package/public/assets/{integrations-hkSW8CzM.js → integrations-DLzJpt9G.js} +2 -2
- package/public/assets/{integrations-hkSW8CzM.js.map → integrations-DLzJpt9G.js.map} +1 -1
- package/public/assets/{maintenance-FAAMGpuZ.js → maintenance-B6_0-ECm.js} +2 -2
- package/public/assets/{maintenance-FAAMGpuZ.js.map → maintenance-B6_0-ECm.js.map} +1 -1
- package/public/assets/{managed-agents-CVca-s3t.js → managed-agents-CpzKXDtV.js} +2 -2
- package/public/assets/{managed-agents-CVca-s3t.js.map → managed-agents-CpzKXDtV.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-BPW4OQo9.js → markdown-preview-impl-DxBnlJ2k.js} +2 -2
- package/public/assets/{markdown-preview-impl-BPW4OQo9.js.map → markdown-preview-impl-DxBnlJ2k.js.map} +1 -1
- package/public/assets/{memory-BzuDTHC5.js → memory-Bnm7ILWL.js} +2 -2
- package/public/assets/{memory-BzuDTHC5.js.map → memory-Bnm7ILWL.js.map} +1 -1
- package/public/assets/{messages-_W24PNvT.js → messages-D9LkocTc.js} +2 -2
- package/public/assets/{messages-_W24PNvT.js.map → messages-D9LkocTc.js.map} +1 -1
- package/public/assets/{orchestrators-Du6I_n8i.js → orchestrators-C__JfrNr.js} +2 -2
- package/public/assets/{orchestrators-Du6I_n8i.js.map → orchestrators-C__JfrNr.js.map} +1 -1
- package/public/assets/{overview-Dyvm1qpT.js → overview-BQ0b0V7Q.js} +2 -2
- package/public/assets/{overview-Dyvm1qpT.js.map → overview-BQ0b0V7Q.js.map} +1 -1
- package/public/assets/{pairs-B8x1RCNd.js → pairs-ChyeBs0Z.js} +2 -2
- package/public/assets/{pairs-B8x1RCNd.js.map → pairs-ChyeBs0Z.js.map} +1 -1
- package/public/assets/{projects-vt63bwHN.js → projects-v5Kp1Oaq.js} +2 -2
- package/public/assets/{projects-vt63bwHN.js.map → projects-v5Kp1Oaq.js.map} +1 -1
- package/public/assets/{prompt-settings-xVXmvxB5.js → prompt-settings-YhAOlYc6.js} +2 -2
- package/public/assets/{prompt-settings-xVXmvxB5.js.map → prompt-settings-YhAOlYc6.js.map} +1 -1
- package/public/assets/{registry-ypiICauZ.js → registry-DZoAfrzx.js} +2 -2
- package/public/assets/{registry-ypiICauZ.js.map → registry-DZoAfrzx.js.map} +1 -1
- package/public/assets/{security-BfOQ6T5r.js → security-DRCj00eA.js} +2 -2
- package/public/assets/{security-BfOQ6T5r.js.map → security-DRCj00eA.js.map} +1 -1
- package/public/assets/{select-Dx8Ro-A3.js → select-ZOpLw2Fd.js} +2 -2
- package/public/assets/{select-Dx8Ro-A3.js.map → select-ZOpLw2Fd.js.map} +1 -1
- package/public/assets/{settings-87clTx4N.js → settings-DGgwr9WH.js} +2 -2
- package/public/assets/{settings-87clTx4N.js.map → settings-DGgwr9WH.js.map} +1 -1
- package/public/assets/{store-DUfrGvOE.js → store-Bnag1kY-.js} +2 -2
- package/public/assets/{store-DUfrGvOE.js.map → store-Bnag1kY-.js.map} +1 -1
- package/public/assets/{tasks-1Kvp53wh.js → tasks-DUTu8ZhA.js} +2 -2
- package/public/assets/{tasks-1Kvp53wh.js.map → tasks-DUTu8ZhA.js.map} +1 -1
- package/public/assets/{teams-DJWp4AKi.js → teams-BJpaoG2m.js} +2 -2
- package/public/assets/{teams-DJWp4AKi.js.map → teams-BJpaoG2m.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DgMuRAe_.js → terminal-viewer-impl-CQhh6oXa.js} +2 -2
- package/public/assets/{terminal-viewer-impl-DgMuRAe_.js.map → terminal-viewer-impl-CQhh6oXa.js.map} +1 -1
- package/public/assets/types-BiJFuTiL.js +2 -0
- package/public/assets/types-BiJFuTiL.js.map +1 -0
- package/public/assets/{user-avatar-CfAUS3PZ.js → user-avatar-BYGrUx1U.js} +2 -2
- package/public/assets/{user-avatar-CfAUS3PZ.js.map → user-avatar-BYGrUx1U.js.map} +1 -1
- package/public/assets/{work-queue-D-Otxd9u.js → work-queue-BH2-mARc.js} +2 -2
- package/public/assets/{work-queue-D-Otxd9u.js.map → work-queue-BH2-mARc.js.map} +1 -1
- package/public/assets/{workspaces-CIsQqQby.js → workspaces-DldYyXZR.js} +2 -2
- package/public/assets/{workspaces-CIsQqQby.js.map → workspaces-DldYyXZR.js.map} +1 -1
- package/public/index.html +4 -4
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +6 -0
- package/src/branch-landed.ts +17 -3
- package/src/config-store.ts +12 -1
- package/src/db/agents.ts +3 -2
- package/src/db/continuations.ts +13 -3
- package/src/db/index.ts +3 -0
- package/src/db/issue-lifecycle.ts +12 -0
- package/src/db/mappers.ts +17 -0
- package/src/db/messages.ts +9 -4
- package/src/db/migrations.ts +91 -1
- package/src/db/notification-subscriptions.ts +1 -1
- package/src/db/prompt-templates.ts +19 -0
- package/src/db/schema.ts +67 -1
- package/src/db/stats.ts +3 -2
- package/src/db/task-comment-through.ts +112 -0
- package/src/db/task-core.ts +14 -1
- package/src/db/task-entity.ts +629 -0
- package/src/db/task-maintenance.ts +4 -3
- package/src/db/task-memory.ts +180 -0
- package/src/db/tasks.ts +24 -10
- package/src/issue-tracker/adapter.ts +15 -0
- package/src/issue-tracker/github.ts +40 -2
- package/src/issue-tracker/index.ts +1 -0
- package/src/mcp/index.ts +12 -1
- package/src/mcp/tools-spawn.ts +4 -0
- package/src/mcp-dispatch-tools.ts +164 -0
- package/src/mcp-notification-subscription-tools.ts +62 -4
- package/src/mcp-plan-tools.ts +30 -3
- package/src/mcp-release-tools.ts +33 -0
- package/src/mcp-task-memory-tools.ts +132 -0
- package/src/mcp-task-tools.ts +232 -0
- package/src/mcp-task-weave-tools.ts +66 -0
- package/src/notification-router.ts +24 -2
- package/src/notification-rules.ts +4 -0
- package/src/notification-types.ts +23 -0
- package/src/prompt-resolver.ts +66 -3
- package/src/routes/agents-spawn.ts +10 -0
- package/src/routes/index.ts +14 -1
- package/src/routes/plans.ts +10 -2
- package/src/routes/tasks.ts +276 -2
- package/src/routing-policy-store.ts +147 -2
- package/src/self-resume.ts +13 -2
- package/src/services/issue-lifecycle.ts +9 -1
- package/src/services/plan-graph.ts +107 -9
- package/src/services/release-readiness.ts +142 -0
- package/src/services/send-message.ts +31 -0
- package/src/services/spawn-agent.ts +81 -4
- package/src/services/task-context-hydration.ts +361 -0
- package/src/services/task-entity.ts +149 -0
- package/src/services/task-issue-weave.ts +558 -0
- package/src/services/task-lifecycle.ts +465 -0
- package/src/services/task-plan-view.ts +178 -0
- package/src/services/task-semantic-events.ts +137 -0
- package/src/services/transient-agent-reaper.ts +55 -22
- package/src/spawn-targets.ts +27 -2
- package/src/task-dispatcher.ts +461 -0
- package/src/upgrade-install-verify.ts +20 -1
- package/public/assets/display-BWi8DQCp.js +0 -3
- package/public/assets/display-BWi8DQCp.js.map +0 -1
- package/public/assets/index-3ydeNO0c.js +0 -23
- package/public/assets/index-3ydeNO0c.js.map +0 -1
- package/public/assets/types-DorDDvEb.js +0 -2
- package/public/assets/types-DorDDvEb.js.map +0 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { getAgent } from "../db";
|
|
2
|
+
import { emitRelayEvent } from "../events";
|
|
3
|
+
import type { NotificationScope, NotificationType } from "../notification-types";
|
|
4
|
+
import { routeNotification } from "../notification-router";
|
|
5
|
+
import { resolveAudience } from "../notification-rules";
|
|
6
|
+
import type { Task, TaskBlocker, TaskStatus } from "../types";
|
|
7
|
+
|
|
8
|
+
export type TaskSemanticTransition = "created" | "started" | "blocked" | "needs-decision" | "landed" | "closed";
|
|
9
|
+
|
|
10
|
+
const TASK_NOTIFICATION_TYPE: Record<TaskSemanticTransition, NotificationType> = {
|
|
11
|
+
created: "task.created",
|
|
12
|
+
started: "task.started",
|
|
13
|
+
blocked: "task.blocked",
|
|
14
|
+
"needs-decision": "task.needs_decision",
|
|
15
|
+
landed: "task.landed",
|
|
16
|
+
closed: "task.closed",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function taskTransitionForStatus(status: TaskStatus, blockers: TaskBlocker[] | undefined): TaskSemanticTransition | null {
|
|
20
|
+
if (status === "claimed" || status === "in_progress") return "started";
|
|
21
|
+
if (status === "blocked") {
|
|
22
|
+
return blockers?.some((blocker) => blocker.type === "decision") ? "needs-decision" : "blocked";
|
|
23
|
+
}
|
|
24
|
+
if (status === "done" || status === "failed" || status === "canceled") return "closed";
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TaskSemanticNotificationInput {
|
|
29
|
+
task: Task;
|
|
30
|
+
transition: TaskSemanticTransition;
|
|
31
|
+
actor?: string;
|
|
32
|
+
recipients?: string[];
|
|
33
|
+
workspaceId?: string;
|
|
34
|
+
branch?: string;
|
|
35
|
+
sha?: string;
|
|
36
|
+
landKey?: string;
|
|
37
|
+
occurrenceKey?: string;
|
|
38
|
+
outcome?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function emitTaskSemanticNotification(input: TaskSemanticNotificationInput): void {
|
|
42
|
+
const type = TASK_NOTIFICATION_TYPE[input.transition];
|
|
43
|
+
const scope = taskNotificationScope(input);
|
|
44
|
+
const semanticEvent = `task.${input.transition}`;
|
|
45
|
+
const eventData = {
|
|
46
|
+
taskId: input.task.id,
|
|
47
|
+
transition: input.transition,
|
|
48
|
+
semanticEvent,
|
|
49
|
+
title: input.task.title,
|
|
50
|
+
status: input.task.status,
|
|
51
|
+
deliverableStage: input.task.deliverableStage,
|
|
52
|
+
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
|
53
|
+
...(input.branch ? { branch: input.branch } : {}),
|
|
54
|
+
...(input.sha ? { sha: input.sha } : {}),
|
|
55
|
+
...(input.landKey ? { landKey: input.landKey } : {}),
|
|
56
|
+
...(input.outcome ? { outcome: input.outcome } : {}),
|
|
57
|
+
};
|
|
58
|
+
emitRelayEvent({
|
|
59
|
+
type,
|
|
60
|
+
source: input.actor ?? "server",
|
|
61
|
+
subject: String(input.task.id),
|
|
62
|
+
data: eventData as Record<string, unknown>,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const subscriptionAudience = resolveAudience(type, scope).map((member) => ({
|
|
66
|
+
agentId: member.agentId,
|
|
67
|
+
matchedRule: member.matchedRule,
|
|
68
|
+
}));
|
|
69
|
+
const explicit = (input.recipients ?? [])
|
|
70
|
+
.map((agentId) => agentId.trim())
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.map((agentId) => ({ agentId, matchedRule: "task-stakeholder" }));
|
|
73
|
+
|
|
74
|
+
const recipients = [...subscriptionAudience, ...explicit];
|
|
75
|
+
if (recipients.length === 0) return;
|
|
76
|
+
|
|
77
|
+
routeNotification({
|
|
78
|
+
type,
|
|
79
|
+
scope,
|
|
80
|
+
recipients,
|
|
81
|
+
ttlMs: null,
|
|
82
|
+
message: {
|
|
83
|
+
subject: taskNotificationSubject(input),
|
|
84
|
+
body: taskNotificationBody(input),
|
|
85
|
+
payload: {
|
|
86
|
+
kind: "task.transition",
|
|
87
|
+
notification: "task-transition",
|
|
88
|
+
taskId: input.task.id,
|
|
89
|
+
transition: input.transition,
|
|
90
|
+
semanticEvent,
|
|
91
|
+
...(input.outcome ? { outcome: input.outcome } : {}),
|
|
92
|
+
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
|
93
|
+
...(input.branch ? { branch: input.branch } : {}),
|
|
94
|
+
...(input.sha ? { sha: input.sha } : {}),
|
|
95
|
+
},
|
|
96
|
+
idempotencyKey: taskNotificationIdempotencyKey(input),
|
|
97
|
+
replyExpected: false,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function taskNotificationIdempotencyKey(input: TaskSemanticNotificationInput): string | undefined {
|
|
103
|
+
if (input.landKey) return `task:${input.task.id}:transition:${input.transition}:${input.landKey}`;
|
|
104
|
+
if (input.occurrenceKey) return `task:${input.task.id}:transition:${input.transition}:occurrence:${input.occurrenceKey}`;
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function taskNotificationScope(input: TaskSemanticNotificationInput): NotificationScope {
|
|
109
|
+
const owner = input.task.ownerAgentId;
|
|
110
|
+
const ownerAgent = owner ? getAgent(owner) : undefined;
|
|
111
|
+
const teamId = input.task.teamId ?? ownerAgent?.teamId;
|
|
112
|
+
return {
|
|
113
|
+
taskId: String(input.task.id),
|
|
114
|
+
transition: input.transition,
|
|
115
|
+
...(input.landKey ? { landKey: input.landKey } : {}),
|
|
116
|
+
...(owner ? { author: owner, agentId: owner } : {}),
|
|
117
|
+
...(input.task.createdBy ? { parent: input.task.createdBy } : {}),
|
|
118
|
+
...(teamId ? { teamId } : {}),
|
|
119
|
+
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function taskNotificationSubject(input: TaskSemanticNotificationInput): string {
|
|
124
|
+
return `Task #${input.task.id} ${input.transition}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function taskNotificationBody(input: TaskSemanticNotificationInput): string {
|
|
128
|
+
if (input.transition === "landed") {
|
|
129
|
+
const bits = [`Task #${input.task.id} landed: ${input.task.title}`];
|
|
130
|
+
if (input.sha) bits.push(`sha ${input.sha.slice(0, 12)}`);
|
|
131
|
+
const gatesPassed = input.outcome?.gatesPassed;
|
|
132
|
+
if (typeof gatesPassed === "boolean") bits.push(gatesPassed ? "gates passed" : "gates not verified");
|
|
133
|
+
if (input.outcome?.statusAdvanced === true) bits.push("closed");
|
|
134
|
+
return bits.join("; ");
|
|
135
|
+
}
|
|
136
|
+
return `Task #${input.task.id} ${input.transition}: ${input.task.title}`;
|
|
137
|
+
}
|
|
@@ -4,7 +4,52 @@ import { createActivityEvent, getAgent, listAgents, listWorkspaces, recordTermin
|
|
|
4
4
|
import { routeNotification } from "../notification-router";
|
|
5
5
|
import { isProviderBlocked } from "../provider-state";
|
|
6
6
|
import { TERMINAL_WORKSPACE_STATUSES } from "../workspace-phase";
|
|
7
|
-
import type { WorkspaceRecord } from "../types";
|
|
7
|
+
import type { AgentCard, Command, WorkspaceRecord } from "../types";
|
|
8
|
+
|
|
9
|
+
// The single home for "dispose a transient/one-shot agent" — the canonical `agent.shutdown`
|
|
10
|
+
// command + its events/terminal-marker/activity. Extracted so the idle-grace scan below AND
|
|
11
|
+
// the #831 on-land reap signal emit the byte-identical disposal (one shutdown shape, not two).
|
|
12
|
+
// Pure emission: the CALLER owns the decision (idle+grace vs landed+gates) and any dedup.
|
|
13
|
+
export interface TransientShutdownOptions {
|
|
14
|
+
/** Disposal cause stamped on the command, agent.exited event, and terminal marker. */
|
|
15
|
+
reason: string;
|
|
16
|
+
/** Audit attribution (the command's requestedBy + the activity row's maintenanceJobId). */
|
|
17
|
+
requestedBy: string;
|
|
18
|
+
workspaceId?: string;
|
|
19
|
+
workspaceStatus?: string;
|
|
20
|
+
activityTitle: string;
|
|
21
|
+
activityBody: string;
|
|
22
|
+
/** Why the land was considered complete (idle-scan reason or "deliverable-landed"). */
|
|
23
|
+
landReason?: string;
|
|
24
|
+
now?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function requestTransientAgentShutdown(agent: AgentCard, opts: TransientShutdownOptions): Command {
|
|
28
|
+
const now = opts.now ?? Date.now();
|
|
29
|
+
const command = createCommand({
|
|
30
|
+
type: "agent.shutdown",
|
|
31
|
+
source: "system",
|
|
32
|
+
target: agent.id,
|
|
33
|
+
params: { action: "shutdown", agentId: agent.id, graceful: true, timeoutMs: 10_000, reason: opts.reason, requestedBy: opts.requestedBy, requestedAt: now },
|
|
34
|
+
});
|
|
35
|
+
emitRelayEvent({ type: "command.requested", source: command.source, subject: command.id, data: { command } });
|
|
36
|
+
emitRelayEvent({ type: "agent.exited", source: "server", subject: agent.id, data: { agentId: agent.id, parent: agent.spawnedBy, reason: opts.reason, workspaceId: opts.workspaceId, workspaceStatus: opts.workspaceStatus } });
|
|
37
|
+
// #645 — persist a clean terminal marker so the #634 heartbeat-gap watchdog reads this benign
|
|
38
|
+
// completion as a terminus (not a silent death) once the reaped worker stops heartbeating.
|
|
39
|
+
recordTerminalEvent({ agentId: agent.id, reason: opts.reason, kind: "agent.exited", epoch: agent.epoch, createdAt: now });
|
|
40
|
+
createActivityEvent({
|
|
41
|
+
clientId: `transient-agent-reaped-${agent.id}-${now}`,
|
|
42
|
+
kind: "state",
|
|
43
|
+
title: opts.activityTitle,
|
|
44
|
+
body: opts.activityBody,
|
|
45
|
+
meta: agent.id,
|
|
46
|
+
icon: "ti-check",
|
|
47
|
+
view: "agents",
|
|
48
|
+
agentId: agent.id,
|
|
49
|
+
metadata: { source: "server", maintenanceJobId: opts.requestedBy, commandId: command.id, reason: opts.reason, workspaceId: opts.workspaceId, landReason: opts.landReason },
|
|
50
|
+
});
|
|
51
|
+
return command;
|
|
52
|
+
}
|
|
8
53
|
|
|
9
54
|
const envMsOrDefault = (name: string, fallback: number): number => {
|
|
10
55
|
const v = Number(process.env[name]);
|
|
@@ -142,27 +187,15 @@ export function reapTransientAgents(): Record<string, unknown> {
|
|
|
142
187
|
}
|
|
143
188
|
if (entry.lastReapAt) continue;
|
|
144
189
|
entry.lastReapAt = now;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// completion as a terminus (not a silent death) once the reaped worker stops heartbeating.
|
|
155
|
-
recordTerminalEvent({ agentId: agent.id, reason: "transient-complete", kind: "agent.exited", epoch: agent.epoch, createdAt: now });
|
|
156
|
-
createActivityEvent({
|
|
157
|
-
clientId: `transient-agent-reaped-${agent.id}-${now}`,
|
|
158
|
-
kind: "state",
|
|
159
|
-
title: "Transient agent complete",
|
|
160
|
-
body: "Agent idle grace elapsed and branch work is land-complete; shutdown requested",
|
|
161
|
-
meta: agent.id,
|
|
162
|
-
icon: "ti-check",
|
|
163
|
-
view: "agents",
|
|
164
|
-
agentId: agent.id,
|
|
165
|
-
metadata: { source: "server", maintenanceJobId: "transient-agent-reaper", commandId: command.id, reason: "transient-complete", workspaceId: land.workspaceId, landReason: land.reason },
|
|
190
|
+
requestTransientAgentShutdown(agent, {
|
|
191
|
+
reason: "transient-complete",
|
|
192
|
+
requestedBy: "transient-agent-reaper",
|
|
193
|
+
workspaceId: land.workspaceId,
|
|
194
|
+
workspaceStatus: land.status,
|
|
195
|
+
activityTitle: "Transient agent complete",
|
|
196
|
+
activityBody: "Agent idle grace elapsed and branch work is land-complete; shutdown requested",
|
|
197
|
+
landReason: land.reason,
|
|
198
|
+
now,
|
|
166
199
|
});
|
|
167
200
|
reaped.push(agent.id);
|
|
168
201
|
}
|
package/src/spawn-targets.ts
CHANGED
|
@@ -206,6 +206,29 @@ function quotaAdvisoryFor(
|
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
// #839 — compact per-provider quota band view for the task dispatcher. Projects the same
|
|
210
|
+
// advisory the spawn matrix surfaces (band / prefer / blockedNow) down to the fields the
|
|
211
|
+
// dispatcher needs to protect scarce pools and load-shift onto abundant ones. Returns one
|
|
212
|
+
// entry per provider that has quota records; providers without records are simply absent
|
|
213
|
+
// (the dispatcher then treats them as band-unknown = "normal").
|
|
214
|
+
export interface ProviderQuotaBand {
|
|
215
|
+
provider: SpawnProvider;
|
|
216
|
+
band: QuotaBand;
|
|
217
|
+
prefer: boolean;
|
|
218
|
+
blockedNow: boolean;
|
|
219
|
+
recommendedAction: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function providerQuotaBands(now = Date.now()): ProviderQuotaBand[] {
|
|
223
|
+
return [...providerQuotaAdvisories(now).entries()].map(([provider, advisory]) => ({
|
|
224
|
+
provider,
|
|
225
|
+
band: advisory.quotaPenaltyBand,
|
|
226
|
+
prefer: advisory.prefer,
|
|
227
|
+
blockedNow: advisory.blockedNow,
|
|
228
|
+
recommendedAction: advisory.recommendedAction,
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
|
|
209
232
|
function quotaWindowsFor(record: ProviderQuotaRecord, now: number, providerCfg?: ProviderQuotaRoutingConfig): SpawnQuotaBurnWindow[] {
|
|
210
233
|
const quotaByName = new Map((record.quota?.windows ?? []).map((window) => [window.name, window]));
|
|
211
234
|
return (record.burn?.windows ?? [])
|
|
@@ -381,9 +404,11 @@ export function quotaBandFromSignal(
|
|
|
381
404
|
function recommendedAction(input: { quotaPenaltyBand: SpawnQuotaBurnBand; blockedNow: boolean; prefer: boolean; slack: number | null; windows: SpawnQuotaBurnWindow[] }): string {
|
|
382
405
|
if (input.blockedNow) {
|
|
383
406
|
const hasSevenDayBlock = input.windows.some((window) => window.role === "7d" && window.blockedNow);
|
|
407
|
+
// #827 — "at capacity" (util ≥ hardBackstop) is a routing-avoid advisory, not an evidence-based
|
|
408
|
+
// rate-limit hold. Use distinct wording so operators don't confuse it with an HTTP 429 block.
|
|
384
409
|
return hasSevenDayBlock
|
|
385
|
-
? "Quota
|
|
386
|
-
: "5h quota
|
|
410
|
+
? "Quota at capacity (routing-avoid); switch provider or wait for reset unless this provider was explicitly requested."
|
|
411
|
+
: "5h quota at capacity (routing-avoid); wait for reset or use another provider unless the job is urgent.";
|
|
387
412
|
}
|
|
388
413
|
return windowRecommendedAction(input.quotaPenaltyBand, input.slack);
|
|
389
414
|
}
|