agent-relay-server 0.62.2 → 0.63.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 +1 -1
- package/package.json +2 -2
- package/public/assets/{MessageBubble-DRrxfjyT.js → MessageBubble-Cl6ufPHw.js} +2 -2
- package/public/assets/MessageBubble-Cl6ufPHw.js.map +1 -0
- package/public/assets/{PlanGraphPanel-Dp2eYazz.js → PlanGraphPanel-BnfjmkQ8.js} +2 -2
- package/public/assets/{PlanGraphPanel-Dp2eYazz.js.map → PlanGraphPanel-BnfjmkQ8.js.map} +1 -1
- package/public/assets/{activity--t0U2yzq.js → activity-aFxvGLtd.js} +2 -2
- package/public/assets/{activity--t0U2yzq.js.map → activity-aFxvGLtd.js.map} +1 -1
- package/public/assets/{agent-profiles-BMtJYzxn.js → agent-profiles-B4mleUDz.js} +2 -2
- package/public/assets/{agent-profiles-BMtJYzxn.js.map → agent-profiles-B4mleUDz.js.map} +1 -1
- package/public/assets/{agents-Ck9AePlc.js → agents-DW5djToQ.js} +2 -2
- package/public/assets/{agents-Ck9AePlc.js.map → agents-DW5djToQ.js.map} +1 -1
- package/public/assets/{analytics-CzfK387L.js → analytics-CRi6thtg.js} +2 -2
- package/public/assets/{analytics-CzfK387L.js.map → analytics-CRi6thtg.js.map} +1 -1
- package/public/assets/{automation-Cgw8vtE8.js → automation-Do4OpauI.js} +2 -2
- package/public/assets/{automation-Cgw8vtE8.js.map → automation-Do4OpauI.js.map} +1 -1
- package/public/assets/{branch-state-badge-DnEEPtOP.js → branch-state-badge-D3W3w7zz.js} +2 -2
- package/public/assets/{branch-state-badge-DnEEPtOP.js.map → branch-state-badge-D3W3w7zz.js.map} +1 -1
- package/public/assets/{channels-Dc3L2MQs.js → channels-BytdR9NS.js} +2 -2
- package/public/assets/{channels-Dc3L2MQs.js.map → channels-BytdR9NS.js.map} +1 -1
- package/public/assets/{chat-Fc6iJ7sa.js → chat-CvaQ2czL.js} +2 -2
- package/public/assets/{chat-Fc6iJ7sa.js.map → chat-CvaQ2czL.js.map} +1 -1
- package/public/assets/{connectors-Dc2ttGWs.js → connectors-BLQDp8Bc.js} +2 -2
- package/public/assets/{connectors-Dc2ttGWs.js.map → connectors-BLQDp8Bc.js.map} +1 -1
- package/public/assets/{display-DzNvOu1j.js → display-B06I5sqa.js} +3 -3
- package/public/assets/display-B06I5sqa.js.map +1 -0
- package/public/assets/{formatted-body-impl-Bg-gx7rG.js → formatted-body-impl-0Lf8e1Ic.js} +2 -2
- package/public/assets/{formatted-body-impl-Bg-gx7rG.js.map → formatted-body-impl-0Lf8e1Ic.js.map} +1 -1
- package/public/assets/{index-BeDncjN4.js → index-C3su8ezq.js} +6 -6
- package/public/assets/{index-BeDncjN4.js.map → index-C3su8ezq.js.map} +1 -1
- package/public/assets/{insights-c08aW-7n.js → insights-64uExH1E.js} +2 -2
- package/public/assets/{insights-c08aW-7n.js.map → insights-64uExH1E.js.map} +1 -1
- package/public/assets/{integrations-BwpgcqnF.js → integrations-HyC1EaIp.js} +2 -2
- package/public/assets/{integrations-BwpgcqnF.js.map → integrations-HyC1EaIp.js.map} +1 -1
- package/public/assets/{maintenance-CTKP0kWB.js → maintenance-EVBpKH07.js} +2 -2
- package/public/assets/{maintenance-CTKP0kWB.js.map → maintenance-EVBpKH07.js.map} +1 -1
- package/public/assets/{managed-agents-BofCxo8d.js → managed-agents-BLZPxGuw.js} +2 -2
- package/public/assets/{managed-agents-BofCxo8d.js.map → managed-agents-BLZPxGuw.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-CQ-6BIb8.js → markdown-preview-impl-Bg6rCWYN.js} +2 -2
- package/public/assets/{markdown-preview-impl-CQ-6BIb8.js.map → markdown-preview-impl-Bg6rCWYN.js.map} +1 -1
- package/public/assets/{memory-DvfXjMoi.js → memory-Bjr8bgjj.js} +2 -2
- package/public/assets/{memory-DvfXjMoi.js.map → memory-Bjr8bgjj.js.map} +1 -1
- package/public/assets/{messages-C9VsPzZ_.js → messages-CRYw1F4z.js} +2 -2
- package/public/assets/{messages-C9VsPzZ_.js.map → messages-CRYw1F4z.js.map} +1 -1
- package/public/assets/{orchestrators-MffVueeE.js → orchestrators-D-co1vdp.js} +2 -2
- package/public/assets/{orchestrators-MffVueeE.js.map → orchestrators-D-co1vdp.js.map} +1 -1
- package/public/assets/{overview-BSVajefM.js → overview-Ddy5czri.js} +2 -2
- package/public/assets/{overview-BSVajefM.js.map → overview-Ddy5czri.js.map} +1 -1
- package/public/assets/{pairs-B5711-Cs.js → pairs-CFVM8uHL.js} +2 -2
- package/public/assets/{pairs-B5711-Cs.js.map → pairs-CFVM8uHL.js.map} +1 -1
- package/public/assets/{security-DkUwK9Vj.js → security-Df4_76bh.js} +2 -2
- package/public/assets/{security-DkUwK9Vj.js.map → security-Df4_76bh.js.map} +1 -1
- package/public/assets/{select-DrM2RRiT.js → select-Cq3BBnL9.js} +2 -2
- package/public/assets/{select-DrM2RRiT.js.map → select-Cq3BBnL9.js.map} +1 -1
- package/public/assets/{settings-DdKCb-vy.js → settings-DOLEYXZL.js} +2 -2
- package/public/assets/{settings-DdKCb-vy.js.map → settings-DOLEYXZL.js.map} +1 -1
- package/public/assets/{store-iTiheuTD.js → store-C1E8jmNE.js} +4 -4
- package/public/assets/store-C1E8jmNE.js.map +1 -0
- package/public/assets/{tasks-e1y1PFfO.js → tasks-BcsmPi9_.js} +2 -2
- package/public/assets/{tasks-e1y1PFfO.js.map → tasks-BcsmPi9_.js.map} +1 -1
- package/public/assets/{teams-BUcxoGC_.js → teams-BQrV3eKI.js} +2 -2
- package/public/assets/{teams-BUcxoGC_.js.map → teams-BQrV3eKI.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-CmZjQOB2.js → terminal-viewer-impl-Dle04qpH.js} +2 -2
- package/public/assets/{terminal-viewer-impl-CmZjQOB2.js.map → terminal-viewer-impl-Dle04qpH.js.map} +1 -1
- package/public/assets/{work-queue-CXBQx43n.js → work-queue-BLhTLg4o.js} +2 -2
- package/public/assets/{work-queue-CXBQx43n.js.map → work-queue-BLhTLg4o.js.map} +1 -1
- package/public/assets/{workspaces-D2EfZh9a.js → workspaces-4NCNTmRQ.js} +3 -3
- package/public/assets/{workspaces-D2EfZh9a.js.map → workspaces-4NCNTmRQ.js.map} +1 -1
- package/public/index.html +3 -3
- package/scripts/orchestrator-spawn-smoke.ts +87 -67
- package/src/agent-ref.ts +8 -6
- package/src/automations/constants.ts +12 -0
- package/src/automations/cron.ts +129 -0
- package/src/automations/crud.ts +84 -0
- package/src/automations/dispatch.ts +275 -0
- package/src/automations/index.ts +5 -0
- package/src/automations/reconcile.ts +245 -0
- package/src/automations/storage.ts +181 -0
- package/src/automations/types.ts +16 -0
- package/src/automations/validation.ts +140 -0
- package/src/automations.ts +1 -1045
- package/src/db/activity.ts +1 -1
- package/src/db/agent-predicates.ts +9 -0
- package/src/db/agent-search.ts +2 -2
- package/src/db/agents.ts +13 -41
- package/src/db/artifacts.ts +8 -8
- package/src/db/channel-identity.ts +38 -0
- package/src/db/channels.ts +37 -61
- package/src/db/continuation-archives.ts +4 -4
- package/src/db/delivery.ts +75 -30
- package/src/db/inbox.ts +1 -1
- package/src/db/integrations.ts +4 -4
- package/src/db/issue-lifecycle.ts +6 -6
- package/src/db/mappers.ts +1 -2
- package/src/db/merge-lease.ts +3 -3
- package/src/db/message-claims.ts +16 -0
- package/src/db/message-queries.ts +8 -0
- package/src/db/message-reads.ts +157 -18
- package/src/db/messages.ts +23 -48
- package/src/db/notification-subscriptions.ts +2 -2
- package/src/db/orchestrators.ts +4 -4
- package/src/db/pair-maintenance.ts +9 -0
- package/src/db/pairs.ts +10 -18
- package/src/db/plans.ts +1 -1
- package/src/db/policy-runtime.ts +14 -0
- package/src/db/project-entries.ts +1 -1
- package/src/db/projects.ts +2 -2
- package/src/db/shared.ts +7 -0
- package/src/db/stats.ts +2 -2
- package/src/db/task-core.ts +18 -0
- package/src/db/task-maintenance.ts +24 -0
- package/src/db/tasks.ts +5 -17
- package/src/db/teams.ts +3 -3
- package/src/db/workspaces.ts +6 -6
- package/src/issue-tracker/github.ts +3 -3
- package/src/maintenance/constants.ts +35 -0
- package/src/maintenance/events.ts +32 -0
- package/src/maintenance/index.ts +7 -0
- package/src/maintenance/jobs.ts +340 -0
- package/src/maintenance/orphaned-sessions.ts +86 -0
- package/src/maintenance/scheduler.ts +231 -0
- package/src/maintenance/teams.ts +65 -0
- package/src/maintenance/types.ts +31 -0
- package/src/maintenance/workspaces.ts +251 -0
- package/src/maintenance.ts +1 -1099
- package/src/mcp/identity.ts +131 -0
- package/src/mcp/index.ts +208 -0
- package/src/mcp/tools-messaging.ts +459 -0
- package/src/mcp/tools-spawn.ts +145 -0
- package/src/mcp/tools-workspace.ts +227 -0
- package/src/mcp/types.ts +24 -0
- package/src/mcp/validation.ts +104 -0
- package/src/mcp.ts +1 -1319
- package/src/notification-router.ts +2 -2
- package/src/notification-rules.ts +2 -2
- package/src/project-ingest.ts +3 -3
- package/src/project-instructions.ts +1 -1
- package/src/provider-config-store.ts +3 -3
- package/src/provider-state.ts +1 -1
- package/src/reviewer-pipeline.ts +1 -1
- package/src/self-resume.ts +3 -7
- package/src/services/agent-action.ts +2 -2
- package/src/services/issue-activity.ts +0 -4
- package/src/services/issue-associations.ts +2 -2
- package/src/services/issue-lifecycle.ts +7 -7
- package/src/services/managed-running.ts +1 -1
- package/src/services/parity-harness.ts +3 -3
- package/src/services/plan-graph.ts +3 -3
- package/src/services/send-message.ts +16 -12
- package/src/services/shutdown-agent.ts +2 -2
- package/src/services/team-templates.ts +2 -2
- package/src/services/team.ts +2 -2
- package/src/settings/modules.ts +1 -1
- package/src/settings/registry.ts +3 -3
- package/src/spawn-command.ts +1 -1
- package/src/sse.ts +0 -4
- package/src/workspace-actions.ts +1 -1
- package/src/workspace-probe.ts +3 -7
- package/public/assets/MessageBubble-DRrxfjyT.js.map +0 -1
- package/public/assets/display-DzNvOu1j.js.map +0 -1
- package/public/assets/store-iTiheuTD.js.map +0 -1
package/src/db/activity.ts
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AgentCard } from "../types";
|
|
2
|
+
|
|
3
|
+
export function isDeliveryAgent(agent: AgentCard): boolean {
|
|
4
|
+
return agent.status !== "offline" &&
|
|
5
|
+
agent.id !== "user" &&
|
|
6
|
+
agent.id !== "system" &&
|
|
7
|
+
agent.kind !== "channel" &&
|
|
8
|
+
agent.meta?.kind !== "channel";
|
|
9
|
+
}
|
package/src/db/agent-search.ts
CHANGED
|
@@ -105,9 +105,9 @@ export interface AgentSearchSort {
|
|
|
105
105
|
limit?: number;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
const AGENT_SEARCH_MAX_LIMIT = 200;
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
function searchValues(value: string | string[] | undefined): string[] {
|
|
111
111
|
if (value == null) return [];
|
|
112
112
|
return (Array.isArray(value) ? value : [value]).filter((v): v is string => typeof v === "string" && v.length > 0);
|
|
113
113
|
}
|
package/src/db/agents.ts
CHANGED
|
@@ -14,13 +14,15 @@ import {
|
|
|
14
14
|
} from "../contracts";
|
|
15
15
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
16
16
|
import { matchAgents } from "../agent-ref";
|
|
17
|
-
import {
|
|
17
|
+
import { expectedChannelAgentId } from "./channel-identity.ts";
|
|
18
|
+
import { evaluatePoolBindings, upsertChannelForAgent } from "./channels.ts";
|
|
18
19
|
import { seedContinuationObjectiveFromSpawnPrompt } from "./continuations.ts";
|
|
19
20
|
import { messageTaskClosed } from "./claim-sql.ts";
|
|
20
21
|
import { ValidationError, getDb } from "./connection.ts";
|
|
21
22
|
import { rowToAgent, rowToContextSnapshot } from "./mappers.ts";
|
|
22
|
-
import { closeOpenPairsForAgent } from "./
|
|
23
|
-
import {
|
|
23
|
+
import { closeOpenPairsForAgent } from "./pair-maintenance.ts";
|
|
24
|
+
import { parseStringArray } from "./shared.ts";
|
|
25
|
+
import { settleSingleTargetOnDemandTasks } from "./task-maintenance.ts";
|
|
24
26
|
import { electWorkspaceStewards, electWorkspaceStewardsForAgent } from "./workspaces.ts";
|
|
25
27
|
import { DEFAULT_SELF_RESUME_CONFIG } from "../self-resume-config";
|
|
26
28
|
import type { TokenRevokeReason } from "../security";
|
|
@@ -89,17 +91,13 @@ import type {
|
|
|
89
91
|
|
|
90
92
|
const CONTEXT_SNAPSHOT_DEBOUNCE_MS = 60_000;
|
|
91
93
|
|
|
92
|
-
export
|
|
93
|
-
const parsed = parseJson<unknown>(raw, []);
|
|
94
|
-
if (!Array.isArray(parsed)) return [];
|
|
95
|
-
return parsed.filter((value): value is string => typeof value === "string");
|
|
96
|
-
}
|
|
94
|
+
export { parseStringArray } from "./shared.ts";
|
|
97
95
|
|
|
98
96
|
export function normalizeTags(tags: string[] | undefined): string[] {
|
|
99
97
|
return [...new Set((tags ?? []).map((tag) => tag.trim()).filter(Boolean))];
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
|
|
100
|
+
function inferAgentKind(input: Pick<RegisterAgentInput, "id" | "kind" | "tags" | "capabilities" | "meta">): AgentKind {
|
|
103
101
|
if (input.kind) return input.kind;
|
|
104
102
|
if (input.id === "user") return "user";
|
|
105
103
|
if (input.id === "system") return "system";
|
|
@@ -110,7 +108,7 @@ export function inferAgentKind(input: Pick<RegisterAgentInput, "id" | "kind" | "
|
|
|
110
108
|
return "provider";
|
|
111
109
|
}
|
|
112
110
|
|
|
113
|
-
|
|
111
|
+
function inferProviderTag(input: RegisterAgentInput): "claude" | "codex" | undefined {
|
|
114
112
|
if (inferAgentKind(input) !== "provider") return undefined;
|
|
115
113
|
const meta = input.meta ?? {};
|
|
116
114
|
const values = [
|
|
@@ -134,7 +132,7 @@ export function inferProviderTag(input: RegisterAgentInput): "claude" | "codex"
|
|
|
134
132
|
return undefined;
|
|
135
133
|
}
|
|
136
134
|
|
|
137
|
-
|
|
135
|
+
function tagsWithProvider(input: RegisterAgentInput): string[] {
|
|
138
136
|
const tags = normalizeTags(input.tags);
|
|
139
137
|
const provider = inferProviderTag(input);
|
|
140
138
|
if (!provider || tags.includes(provider)) return tags;
|
|
@@ -392,14 +390,14 @@ export function listContextSnapshots(agentId: string, options: { since?: number;
|
|
|
392
390
|
return (getDb().query(sql).all(...params) as any[]).map(rowToContextSnapshot);
|
|
393
391
|
}
|
|
394
392
|
|
|
395
|
-
|
|
393
|
+
function pruneContextSnapshots(agentId?: string, olderThan = Date.now() - DAY_MS): number {
|
|
396
394
|
const result = agentId
|
|
397
395
|
? getDb().query("DELETE FROM context_snapshots WHERE agent_id = ? AND captured_at < ?").run(agentId, olderThan)
|
|
398
396
|
: getDb().query("DELETE FROM context_snapshots WHERE captured_at < ?").run(olderThan);
|
|
399
397
|
return result.changes;
|
|
400
398
|
}
|
|
401
399
|
|
|
402
|
-
|
|
400
|
+
function recordContextSnapshot(agentId: string, context: ContextState, now: number): void {
|
|
403
401
|
const latest = getDb()
|
|
404
402
|
.query("SELECT captured_at FROM context_snapshots WHERE agent_id = ? ORDER BY captured_at DESC LIMIT 1")
|
|
405
403
|
.get(agentId) as { captured_at: number } | undefined;
|
|
@@ -464,33 +462,7 @@ export function reapStaleAgents(ttlMs: number = STALE_TTL_MS): string[] {
|
|
|
464
462
|
return rows.map((r: any) => r.id);
|
|
465
463
|
}
|
|
466
464
|
|
|
467
|
-
|
|
468
|
-
// ephemeral agent spawned just for that task. When that agent's claim is released —
|
|
469
|
-
// clean shutdown, prune, lease expiry, or orphan-grace — the task must NOT return to
|
|
470
|
-
// the claimable pool: its target is a unique `label:automation-…` that no other agent
|
|
471
|
-
// will ever match, so a re-opened task lingers forever as an orphaned claim. Resolve
|
|
472
|
-
// those as done instead; automation reconcile then settles the run as succeeded.
|
|
473
|
-
// Run this BEFORE the generic re-open UPDATE at each release site, with the same WHERE
|
|
474
|
-
// condition: it flips matching single-target tasks to 'done', which the subsequent
|
|
475
|
-
// re-open (gated on status IN claimed/in_progress/blocked/orphaned) then skips.
|
|
476
|
-
export function settleSingleTargetOnDemandTasks(condition: string, params: any[], now: number, reason: string): void {
|
|
477
|
-
const selectSql = `${TASK_SELECT} WHERE (${condition}) AND json_extract(metadata, '$.targetMode') = 'on_demand_agent' AND status IN ('claimed', 'in_progress', 'blocked', 'orphaned')`;
|
|
478
|
-
const rows = getDb().query(selectSql).all(...params) as any[];
|
|
479
|
-
if (rows.length === 0) return;
|
|
480
|
-
getDb().query(
|
|
481
|
-
`UPDATE tasks SET status = 'done', claim_expires_at = NULL, updated_at = ?, last_seen_at = ? WHERE (${condition}) AND json_extract(metadata, '$.targetMode') = 'on_demand_agent' AND status IN ('claimed', 'in_progress', 'blocked', 'orphaned')`
|
|
482
|
-
).run(now, now, ...params);
|
|
483
|
-
for (const row of rows) {
|
|
484
|
-
insertTaskEvent(row.id, {
|
|
485
|
-
source: "agent-relay",
|
|
486
|
-
type: "task.auto-completed",
|
|
487
|
-
severity: row.severity,
|
|
488
|
-
title: "On-demand task auto-resolved",
|
|
489
|
-
body: `On-demand agent ${row.claimed_by ?? "(unknown)"} exited (${reason}); task resolved so it does not orphan`,
|
|
490
|
-
metadata: { agentId: row.claimed_by, reason, completedBy: "relay" },
|
|
491
|
-
}, now);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
465
|
+
export { settleSingleTargetOnDemandTasks } from "./task-maintenance.ts";
|
|
494
466
|
|
|
495
467
|
export function pruneOfflineAgents(maxOfflineMs: number = DAY_MS): string[] {
|
|
496
468
|
const cutoff = Date.now() - maxOfflineMs;
|
|
@@ -578,7 +550,7 @@ export function revokeRuntimeTokensForAgent(agentId: string, now = Date.now(), r
|
|
|
578
550
|
return rows.map((item) => item.jti);
|
|
579
551
|
}
|
|
580
552
|
|
|
581
|
-
|
|
553
|
+
function runtimeTokenJtisFromMeta(meta: Record<string, unknown>): string[] {
|
|
582
554
|
const jtis = new Set<string>();
|
|
583
555
|
const auth = isRecord(meta.auth) ? meta.auth : undefined;
|
|
584
556
|
const direct = typeof auth?.jti === "string" ? auth.jti : typeof meta.runtimeTokenJti === "string" ? meta.runtimeTokenJti : undefined;
|
package/src/db/artifacts.ts
CHANGED
|
@@ -82,28 +82,28 @@ import type {
|
|
|
82
82
|
// --- Artifacts ---
|
|
83
83
|
|
|
84
84
|
export const VALID_ARTIFACT_KINDS = new Set<ArtifactKind>(["image", "audio", "video", "document", "archive", "other"]);
|
|
85
|
-
|
|
85
|
+
const VALID_ARTIFACT_VISIBILITIES = new Set<ArtifactVisibility>(["private", "project", "org"]);
|
|
86
86
|
export const VALID_ARTIFACT_SENSITIVITIES = new Set<ArtifactSensitivity>(["public", "normal", "sensitive", "secret"]);
|
|
87
|
-
|
|
87
|
+
const VALID_ARTIFACT_LINK_ENTITY_TYPES = new Set<ArtifactLink["entityType"]>(["message", "task", "recipeRun", "recipeStep", "channelEvent"]);
|
|
88
88
|
export const VALID_ARTIFACT_ROLES = new Set<NonNullable<ArtifactLink["role"]>>(["media", "patch", "report", "log", "output", "input"]);
|
|
89
89
|
|
|
90
90
|
export function artifactId(): string {
|
|
91
91
|
return `art_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
function artifactLinkId(): string {
|
|
95
95
|
return `alink_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
function normalizeArtifactKind(kind: unknown): ArtifactKind {
|
|
99
99
|
return typeof kind === "string" && VALID_ARTIFACT_KINDS.has(kind as ArtifactKind) ? kind as ArtifactKind : "other";
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
function normalizeArtifactVisibility(visibility: unknown): ArtifactVisibility {
|
|
103
103
|
return typeof visibility === "string" && VALID_ARTIFACT_VISIBILITIES.has(visibility as ArtifactVisibility) ? visibility as ArtifactVisibility : "project";
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
function normalizeArtifactSensitivity(sensitivity: unknown): ArtifactSensitivity {
|
|
107
107
|
return typeof sensitivity === "string" && VALID_ARTIFACT_SENSITIVITIES.has(sensitivity as ArtifactSensitivity) ? sensitivity as ArtifactSensitivity : "normal";
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -212,7 +212,7 @@ export function deleteArtifact(id: string): boolean {
|
|
|
212
212
|
})();
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
215
|
+
function listArtifactLinks(artifactId: string): ArtifactLink[] {
|
|
216
216
|
return (getDb().query("SELECT * FROM artifact_links WHERE artifact_id = ? ORDER BY created_at ASC").all(artifactId) as any[]).map(rowToArtifactLink);
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -271,7 +271,7 @@ export function artifactBlobReferenceCount(digest: string): number {
|
|
|
271
271
|
return row.count ?? 0;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
function unreferencedArtifactBlobs(): ArtifactBlob[] {
|
|
275
275
|
return (getDb().query(`
|
|
276
276
|
SELECT b.*
|
|
277
277
|
FROM artifact_blobs b
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { stringValue } from "agent-relay-sdk";
|
|
2
|
+
import type { AgentCard, RegisterAgentInput } from "../types";
|
|
3
|
+
|
|
4
|
+
export function channelProviderForAgent(agent: AgentCard): string {
|
|
5
|
+
return stringValue(agent.meta?.provider) ??
|
|
6
|
+
stringValue(agent.meta?.channelType) ??
|
|
7
|
+
stringValue(agent.meta?.transport) ??
|
|
8
|
+
agent.tags.find((tag) => tag.startsWith("channel:"))?.slice("channel:".length) ??
|
|
9
|
+
"custom";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function channelProviderForInput(input: Pick<RegisterAgentInput, "tags" | "meta">): string {
|
|
13
|
+
return stringValue(input.meta?.provider) ??
|
|
14
|
+
stringValue(input.meta?.channelType) ??
|
|
15
|
+
stringValue(input.meta?.transport) ??
|
|
16
|
+
(input.tags ?? []).find((tag) => tag.startsWith("channel:"))?.slice("channel:".length) ??
|
|
17
|
+
"custom";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function channelAccountIdForAgent(agent: AgentCard, provider: string): string {
|
|
21
|
+
const accountId = stringValue(agent.meta?.accountId);
|
|
22
|
+
if (accountId) return accountId;
|
|
23
|
+
const prefix = `${provider}:`;
|
|
24
|
+
return agent.id.startsWith(prefix) ? agent.id.slice(prefix.length) : agent.id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function channelAccountIdForInput(input: Pick<RegisterAgentInput, "id" | "meta">, provider: string): string {
|
|
28
|
+
const accountId = stringValue(input.meta?.accountId);
|
|
29
|
+
if (accountId) return accountId;
|
|
30
|
+
const prefix = `${provider}:`;
|
|
31
|
+
return input.id.startsWith(prefix) ? input.id.slice(prefix.length) : input.id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function expectedChannelAgentId(input: Pick<RegisterAgentInput, "id" | "tags" | "meta">): string {
|
|
35
|
+
const provider = channelProviderForInput(input);
|
|
36
|
+
const accountId = channelAccountIdForInput(input, provider);
|
|
37
|
+
return `${provider}:${accountId}`;
|
|
38
|
+
}
|
package/src/db/channels.ts
CHANGED
|
@@ -15,10 +15,12 @@ import {
|
|
|
15
15
|
} from "../contracts";
|
|
16
16
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
17
17
|
import { matchAgents } from "../agent-ref";
|
|
18
|
-
import {
|
|
18
|
+
import { isDeliveryAgent } from "./agent-predicates.ts";
|
|
19
|
+
import { channelAccountIdForAgent, channelProviderForAgent, expectedChannelAgentId } from "./channel-identity.ts";
|
|
19
20
|
import { ValidationError, getDb } from "./connection.ts";
|
|
20
|
-
import {
|
|
21
|
-
import { runningAgentForPolicy } from "./
|
|
21
|
+
import { rowToAgent } from "./mappers.ts";
|
|
22
|
+
import { runningAgentForPolicy } from "./policy-runtime.ts";
|
|
23
|
+
import { parseStringArray } from "./shared.ts";
|
|
22
24
|
import type {
|
|
23
25
|
AgentCard,
|
|
24
26
|
ActivityEvent,
|
|
@@ -82,7 +84,9 @@ import type {
|
|
|
82
84
|
WorkspaceStatus,
|
|
83
85
|
} from "../types";
|
|
84
86
|
|
|
85
|
-
export
|
|
87
|
+
export { channelProviderForAgent, expectedChannelAgentId } from "./channel-identity.ts";
|
|
88
|
+
|
|
89
|
+
function rowToChannelBinding(row: any): ChannelBinding {
|
|
86
90
|
const target = row.target_type === "broadcast"
|
|
87
91
|
? { type: "broadcast" } as ChannelRouteTarget
|
|
88
92
|
: { type: row.target_type, id: row.target_id } as ChannelRouteTarget;
|
|
@@ -105,7 +109,7 @@ export function rowToChannelBinding(row: any): ChannelBinding {
|
|
|
105
109
|
return binding;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
function bindingTargetToLegacyTarget(target: ChannelRouteTarget): string {
|
|
109
113
|
if (target.type === "agent") return target.id;
|
|
110
114
|
if (target.type === "label") return `label:${target.id}`;
|
|
111
115
|
if (target.type === "team") return `team:${target.id}`;
|
|
@@ -118,7 +122,7 @@ export function bindingTargetToLegacyTarget(target: ChannelRouteTarget): string
|
|
|
118
122
|
return "";
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
function configuredChannelsForAgent(agent: AgentCard): string[] {
|
|
122
126
|
const channels = agent.meta?.channels;
|
|
123
127
|
if (!Array.isArray(channels)) return [];
|
|
124
128
|
return channels
|
|
@@ -127,7 +131,7 @@ export function configuredChannelsForAgent(agent: AgentCard): string[] {
|
|
|
127
131
|
.filter((item) => item.length > 0);
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
|
|
134
|
+
function channelEntryMatches(channelId: string, entry: string): boolean {
|
|
131
135
|
const normalized = entry.trim();
|
|
132
136
|
if (!normalized) return false;
|
|
133
137
|
if (normalized === channelId) return true;
|
|
@@ -135,27 +139,36 @@ export function channelEntryMatches(channelId: string, entry: string): boolean {
|
|
|
135
139
|
return channelId.startsWith(`${normalized}:`);
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
function agentCanServeChannel(agent: AgentCard, channelId?: string): boolean {
|
|
139
143
|
if (!channelId) return true;
|
|
140
144
|
const channels = configuredChannelsForAgent(agent);
|
|
141
145
|
if (channels.length === 0) return true;
|
|
142
146
|
return channels.some((entry) => channelEntryMatches(channelId, entry));
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
const
|
|
149
|
+
function getChannelAgent(id: string): AgentCard | null {
|
|
150
|
+
const row = getDb().query("SELECT * FROM agents WHERE id = ?").get(id) as any;
|
|
151
|
+
return row ? rowToAgent(row) : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function listChannelAgents(): AgentCard[] {
|
|
155
|
+
return (getDb().query("SELECT * FROM agents ORDER BY last_seen DESC").all() as any[]).map(rowToAgent);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function channelTargetMatches(target: ChannelRouteTarget, channelId?: string): AgentCard[] {
|
|
159
|
+
const candidates = listChannelAgents().filter((agent) => (
|
|
147
160
|
agent.id !== "user" &&
|
|
148
161
|
agent.id !== "system" &&
|
|
149
162
|
agent.kind !== "channel" &&
|
|
150
163
|
agent.meta?.kind !== "channel"
|
|
151
164
|
));
|
|
152
165
|
if (target.type === "agent" || target.type === "orchestrator") {
|
|
153
|
-
const agent =
|
|
166
|
+
const agent = getChannelAgent(target.id);
|
|
154
167
|
return agent ? [agent] : [];
|
|
155
168
|
}
|
|
156
169
|
if (target.type === "policy") {
|
|
157
170
|
const agentId = runningAgentForPolicy(target.id);
|
|
158
|
-
const agent = agentId ?
|
|
171
|
+
const agent = agentId ? getChannelAgent(agentId) : null;
|
|
159
172
|
return agent ? [agent] : [];
|
|
160
173
|
}
|
|
161
174
|
const channelCandidates = candidates.filter((agent) => agentCanServeChannel(agent, channelId));
|
|
@@ -168,15 +181,15 @@ export function channelTargetMatches(target: ChannelRouteTarget, channelId?: str
|
|
|
168
181
|
return [];
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
|
|
184
|
+
function poolSelectorMatches(selector: string, candidates: AgentCard[]): AgentCard[] {
|
|
172
185
|
if (selector.startsWith("label:")) return candidates.filter((a) => a.label === selector.slice("label:".length));
|
|
173
186
|
if (selector.startsWith("tag:")) return candidates.filter((a) => a.tags.includes(selector.slice("tag:".length)));
|
|
174
187
|
if (selector.startsWith("cap:")) return candidates.filter((a) => a.capabilities.includes(selector.slice("cap:".length)));
|
|
175
|
-
const agent =
|
|
188
|
+
const agent = getChannelAgent(selector);
|
|
176
189
|
return agent ? [agent] : [];
|
|
177
190
|
}
|
|
178
191
|
|
|
179
|
-
|
|
192
|
+
function channelTargetMatchSnapshot(agent: AgentCard): ChannelTargetHealth["matches"][number] {
|
|
180
193
|
return {
|
|
181
194
|
id: agent.id,
|
|
182
195
|
name: agent.name,
|
|
@@ -189,15 +202,15 @@ export function channelTargetMatchSnapshot(agent: AgentCard): ChannelTargetHealt
|
|
|
189
202
|
};
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
|
|
205
|
+
function isHealthyChannelTarget(agent: AgentCard, now: number): boolean {
|
|
193
206
|
return isDeliveryAgent(agent) && agent.ready && agent.lastSeen > now - STALE_TTL_MS;
|
|
194
207
|
}
|
|
195
208
|
|
|
196
|
-
|
|
209
|
+
function describeTarget(target: ChannelRouteTarget): string {
|
|
197
210
|
return bindingTargetToLegacyTarget(target);
|
|
198
211
|
}
|
|
199
212
|
|
|
200
|
-
|
|
213
|
+
function channelTargetHealth(binding: ChannelBinding, now: number = Date.now()): ChannelTargetHealth {
|
|
201
214
|
const target = binding.target;
|
|
202
215
|
const targetLabel = describeTarget(target);
|
|
203
216
|
const matches = channelTargetMatches(target, binding.channelId);
|
|
@@ -209,7 +222,7 @@ export function channelTargetHealth(binding: ChannelBinding, now: number = Date.
|
|
|
209
222
|
if (matches.length === 0) return { status: "error", detail: `Pool ${targetLabel}: no eligible agents`, target, matches: [] };
|
|
210
223
|
return { status: "error", detail: `Pool ${targetLabel}: ${healthyEligibles.length} eligible but slot unclaimed`, target, matches: snapshots };
|
|
211
224
|
}
|
|
212
|
-
const holder =
|
|
225
|
+
const holder = getChannelAgent(binding.poolAgentId);
|
|
213
226
|
if (!holder || holder.status === "offline") {
|
|
214
227
|
return { status: "error", detail: `Pool ${targetLabel}: holder ${binding.poolAgentId} is offline`, target, matches: snapshots };
|
|
215
228
|
}
|
|
@@ -271,7 +284,7 @@ export function channelTargetHealth(binding: ChannelBinding, now: number = Date.
|
|
|
271
284
|
};
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
|
|
287
|
+
function rowToChannelSummary(row: any): ChannelSummary {
|
|
275
288
|
const binding = row.binding_id ? rowToChannelBinding({
|
|
276
289
|
id: row.binding_id,
|
|
277
290
|
channel_id: row.id,
|
|
@@ -309,44 +322,7 @@ export function rowToChannelSummary(row: any): ChannelSummary {
|
|
|
309
322
|
};
|
|
310
323
|
}
|
|
311
324
|
|
|
312
|
-
|
|
313
|
-
export function channelProviderForAgent(agent: AgentCard): string {
|
|
314
|
-
return stringValue(agent.meta?.provider) ??
|
|
315
|
-
stringValue(agent.meta?.channelType) ??
|
|
316
|
-
stringValue(agent.meta?.transport) ??
|
|
317
|
-
agent.tags.find((tag) => tag.startsWith("channel:"))?.slice("channel:".length) ??
|
|
318
|
-
"custom";
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function channelProviderForInput(input: Pick<RegisterAgentInput, "tags" | "meta">): string {
|
|
322
|
-
return stringValue(input.meta?.provider) ??
|
|
323
|
-
stringValue(input.meta?.channelType) ??
|
|
324
|
-
stringValue(input.meta?.transport) ??
|
|
325
|
-
(input.tags ?? []).find((tag) => tag.startsWith("channel:"))?.slice("channel:".length) ??
|
|
326
|
-
"custom";
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
export function channelAccountIdForAgent(agent: AgentCard, provider: string): string {
|
|
330
|
-
const accountId = stringValue(agent.meta?.accountId);
|
|
331
|
-
if (accountId) return accountId;
|
|
332
|
-
const prefix = `${provider}:`;
|
|
333
|
-
return agent.id.startsWith(prefix) ? agent.id.slice(prefix.length) : agent.id;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function channelAccountIdForInput(input: Pick<RegisterAgentInput, "id" | "meta">, provider: string): string {
|
|
337
|
-
const accountId = stringValue(input.meta?.accountId);
|
|
338
|
-
if (accountId) return accountId;
|
|
339
|
-
const prefix = `${provider}:`;
|
|
340
|
-
return input.id.startsWith(prefix) ? input.id.slice(prefix.length) : input.id;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export function expectedChannelAgentId(input: Pick<RegisterAgentInput, "id" | "tags" | "meta">): string {
|
|
344
|
-
const provider = channelProviderForInput(input);
|
|
345
|
-
const accountId = channelAccountIdForInput(input, provider);
|
|
346
|
-
return `${provider}:${accountId}`;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export function channelDirectionForAgent(agent: AgentCard): ChannelSummary["direction"] {
|
|
325
|
+
function channelDirectionForAgent(agent: AgentCard): ChannelSummary["direction"] {
|
|
350
326
|
const direction = stringValue(agent.meta?.direction);
|
|
351
327
|
return direction === "inbound" || direction === "outbound" || direction === "bidirectional" ? direction : "bidirectional";
|
|
352
328
|
}
|
|
@@ -497,7 +473,7 @@ export function upsertChannelBinding(input: {
|
|
|
497
473
|
return rowToChannelBinding(row);
|
|
498
474
|
}
|
|
499
475
|
|
|
500
|
-
|
|
476
|
+
interface PoolBindingChange {
|
|
501
477
|
bindingId: string;
|
|
502
478
|
channelId: string;
|
|
503
479
|
previousAgentId: string | null;
|
|
@@ -517,7 +493,7 @@ export function evaluatePoolBindings(now: number = Date.now()): PoolBindingChang
|
|
|
517
493
|
|
|
518
494
|
let holderValid = false;
|
|
519
495
|
if (currentAgentId) {
|
|
520
|
-
const holder =
|
|
496
|
+
const holder = getChannelAgent(currentAgentId);
|
|
521
497
|
if (holder && holder.status !== "offline" && holder.ready && holder.lastSeen > now - STALE_TTL_MS && agentCanServeChannel(holder, channelId)) {
|
|
522
498
|
if (currentEpoch === null || holder.epoch === currentEpoch) {
|
|
523
499
|
getDb().query("UPDATE channel_bindings SET pool_claim_expires_at = ? WHERE id = ?")
|
|
@@ -534,7 +510,7 @@ export function evaluatePoolBindings(now: number = Date.now()): PoolBindingChang
|
|
|
534
510
|
}
|
|
535
511
|
|
|
536
512
|
if (!holderValid) {
|
|
537
|
-
const candidates =
|
|
513
|
+
const candidates = listChannelAgents().filter((a) =>
|
|
538
514
|
a.id !== "user" && a.id !== "system" && a.kind !== "channel" && a.meta?.kind !== "channel"
|
|
539
515
|
);
|
|
540
516
|
const eligible = poolSelectorMatches(selector, candidates)
|
|
@@ -2,7 +2,7 @@ import { MAX_BODY_BYTES } from "../config";
|
|
|
2
2
|
import { getContinuationEnvelope } from "./continuations.ts";
|
|
3
3
|
import { getDb, ValidationError } from "./connection.ts";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface ContinuationArchive {
|
|
6
6
|
id: number;
|
|
7
7
|
ref: string;
|
|
8
8
|
agentId: string;
|
|
@@ -11,7 +11,7 @@ export interface ContinuationArchive {
|
|
|
11
11
|
createdAt: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
interface ContinuationArchiveSearchResult extends ContinuationArchive {
|
|
15
15
|
score: number | null;
|
|
16
16
|
searchMode: "fts5" | "like";
|
|
17
17
|
}
|
|
@@ -80,7 +80,7 @@ export function archiveContinuationSegment(input: {
|
|
|
80
80
|
return getContinuationArchive(Number(info.lastInsertRowid))!;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
function getContinuationArchive(id: number): ContinuationArchive | null {
|
|
84
84
|
const row = getDb().query("SELECT * FROM continuation_archives WHERE id = ?").get(id) as ContinuationArchiveRow | undefined;
|
|
85
85
|
return row ? rowToArchive(row) : null;
|
|
86
86
|
}
|
|
@@ -115,7 +115,7 @@ export function searchContinuationArchives(input: {
|
|
|
115
115
|
return searchContinuationArchivesLike(agentId, query, limit);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
function continuationArchiveSearchMode(): "fts5" | "like" {
|
|
119
119
|
if (process.env.AGENT_RELAY_DISABLE_CONTINUATION_ARCHIVE_FTS === "1") return "like";
|
|
120
120
|
const row = getDb().query(`
|
|
121
121
|
SELECT 1 FROM sqlite_master
|
package/src/db/delivery.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { isRecord, stringValue, isMechanicalMessageKind } from "agent-relay-sdk"
|
|
|
4
4
|
import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
|
|
5
5
|
import { parseJson } from "../utils";
|
|
6
6
|
import { isLiveIsolatedWorkspace } from "../workspace-phase";
|
|
7
|
+
import { parseChannelRouteTarget } from "../channel-target";
|
|
7
8
|
import {
|
|
8
9
|
CONTRACT_REQUIREMENTS,
|
|
9
10
|
contractCompatibility,
|
|
@@ -15,10 +16,11 @@ import {
|
|
|
15
16
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
16
17
|
import { matchAgents } from "../agent-ref";
|
|
17
18
|
import { getAgent, listAgents } from "./agents.ts";
|
|
18
|
-
import {
|
|
19
|
+
import { isDeliveryAgent } from "./agent-predicates.ts";
|
|
20
|
+
import { channelProviderForAgent } from "./channel-identity.ts";
|
|
19
21
|
import { getDb } from "./connection.ts";
|
|
20
22
|
import { MSG_SELECT, rowToMessage, rowToMessageDeliveryAttempt } from "./mappers.ts";
|
|
21
|
-
import { getMessage } from "./
|
|
23
|
+
import { getMessage } from "./message-queries.ts";
|
|
22
24
|
import type {
|
|
23
25
|
AgentCard,
|
|
24
26
|
ActivityEvent,
|
|
@@ -82,7 +84,7 @@ import type {
|
|
|
82
84
|
WorkspaceStatus,
|
|
83
85
|
} from "../types";
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
function queueDepthLimit(target: string): number {
|
|
86
88
|
const row = getDb().query("SELECT value FROM config WHERE namespace = 'system' AND key = 'message-queue'").get() as { value?: string } | undefined;
|
|
87
89
|
const parsed = row?.value ? parseJson<Record<string, unknown>>(row.value, {}) : {};
|
|
88
90
|
const perTarget = parsed?.maxDepthPerTarget;
|
|
@@ -123,13 +125,7 @@ export function enforceQueueLimit(target: string): void {
|
|
|
123
125
|
})();
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
export
|
|
127
|
-
return agent.status !== "offline" &&
|
|
128
|
-
agent.id !== "user" &&
|
|
129
|
-
agent.id !== "system" &&
|
|
130
|
-
agent.kind !== "channel" &&
|
|
131
|
-
agent.meta?.kind !== "channel";
|
|
132
|
-
}
|
|
128
|
+
export { isDeliveryAgent } from "./agent-predicates.ts";
|
|
133
129
|
|
|
134
130
|
export function isChannelAgentId(agentId: string): boolean {
|
|
135
131
|
const agent = getAgent(agentId);
|
|
@@ -156,27 +152,76 @@ export function legacyChannelTargets(agent: AgentCard | null | undefined): strin
|
|
|
156
152
|
return [...aliases];
|
|
157
153
|
}
|
|
158
154
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
type BroadcastDeliveryContext = Pick<SendMessageInput, "from" | "channel">;
|
|
156
|
+
|
|
157
|
+
function addTargetParticipants(target: string, scope: Set<string>, candidates: AgentCard[]): void {
|
|
158
|
+
const parsed = parseChannelRouteTarget(target);
|
|
159
|
+
switch (parsed.type) {
|
|
160
|
+
case "agent":
|
|
161
|
+
case "orchestrator":
|
|
162
|
+
if (candidates.some((agent) => agent.id === parsed.id)) scope.add(parsed.id);
|
|
163
|
+
return;
|
|
164
|
+
case "tag":
|
|
165
|
+
for (const agent of candidates) if (agent.tags.includes(parsed.id)) scope.add(agent.id);
|
|
166
|
+
return;
|
|
167
|
+
case "capability":
|
|
168
|
+
for (const agent of candidates) if (agent.capabilities.includes(parsed.id)) scope.add(agent.id);
|
|
169
|
+
return;
|
|
170
|
+
case "label":
|
|
171
|
+
for (const agent of candidates) if (agent.label === parsed.id) scope.add(agent.id);
|
|
172
|
+
return;
|
|
173
|
+
case "team":
|
|
174
|
+
for (const agent of candidates) if (agent.teamId === parsed.id) scope.add(agent.id);
|
|
175
|
+
return;
|
|
168
176
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function channelParticipantAgentIds(channel: string, candidates: AgentCard[], senderId?: string): Set<string> {
|
|
180
|
+
const candidateIds = new Set(candidates.map((agent) => agent.id));
|
|
181
|
+
const scope = new Set<string>();
|
|
182
|
+
if (senderId && candidateIds.has(senderId)) scope.add(senderId);
|
|
183
|
+
const rows = getDb().query(`
|
|
184
|
+
SELECT from_agent, to_target, resolved_to_agent
|
|
185
|
+
FROM messages
|
|
186
|
+
WHERE channel = ?
|
|
187
|
+
`).all(channel) as Array<{ from_agent: string; to_target: string; resolved_to_agent?: string | null }>;
|
|
188
|
+
for (const row of rows) {
|
|
189
|
+
if (candidateIds.has(row.from_agent)) scope.add(row.from_agent);
|
|
190
|
+
if (row.resolved_to_agent && candidateIds.has(row.resolved_to_agent)) scope.add(row.resolved_to_agent);
|
|
191
|
+
addTargetParticipants(row.to_target, scope, candidates);
|
|
172
192
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
return scope;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function scopedBroadcastCandidates(candidates: AgentCard[], context?: BroadcastDeliveryContext): AgentCard[] {
|
|
197
|
+
if (context?.channel) {
|
|
198
|
+
const scope = channelParticipantAgentIds(context.channel, candidates, context.from);
|
|
199
|
+
return candidates.filter((agent) => scope.has(agent.id));
|
|
176
200
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
201
|
+
const sender = context?.from ? getAgent(context.from) : null;
|
|
202
|
+
if (sender?.teamId) return candidates.filter((agent) => agent.teamId === sender.teamId);
|
|
203
|
+
return candidates;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function matchingDeliveryAgents(target: string, context?: BroadcastDeliveryContext): AgentCard[] {
|
|
207
|
+
if (!target) return [];
|
|
208
|
+
const candidates = listAgents().filter(isDeliveryAgent);
|
|
209
|
+
const parsed = parseChannelRouteTarget(target);
|
|
210
|
+
switch (parsed.type) {
|
|
211
|
+
case "broadcast":
|
|
212
|
+
return scopedBroadcastCandidates(candidates, context);
|
|
213
|
+
case "agent": {
|
|
214
|
+
const direct = getAgent(parsed.id);
|
|
215
|
+
return direct && isDeliveryAgent(direct) ? [direct] : [];
|
|
216
|
+
}
|
|
217
|
+
case "tag":
|
|
218
|
+
return candidates.filter((agent) => agent.tags.includes(parsed.id));
|
|
219
|
+
case "capability":
|
|
220
|
+
return candidates.filter((agent) => agent.capabilities.includes(parsed.id));
|
|
221
|
+
case "label":
|
|
222
|
+
return candidates.filter((agent) => agent.label === parsed.id);
|
|
223
|
+
case "team":
|
|
224
|
+
return candidates.filter((agent) => agent.teamId === parsed.id);
|
|
180
225
|
}
|
|
181
226
|
return [];
|
|
182
227
|
}
|
|
@@ -192,7 +237,7 @@ export function getMessageDeliveryAttempts(messageId: number, limit = 50): Messa
|
|
|
192
237
|
`).all(messageId, safeLimit) as any[]).map(rowToMessageDeliveryAttempt);
|
|
193
238
|
}
|
|
194
239
|
|
|
195
|
-
|
|
240
|
+
function insertMessageDeliveryAttempt(
|
|
196
241
|
messageId: number,
|
|
197
242
|
input: {
|
|
198
243
|
agentId?: string;
|
|
@@ -219,7 +264,7 @@ export function insertMessageDeliveryAttempt(
|
|
|
219
264
|
);
|
|
220
265
|
}
|
|
221
266
|
|
|
222
|
-
|
|
267
|
+
function setMessageDeliveryState(
|
|
223
268
|
messageId: number,
|
|
224
269
|
input: {
|
|
225
270
|
status: MessageDeliveryStatus;
|