agent-relay-server 0.62.3 → 0.63.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 +4 -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/runner/src/adapter.ts +2 -1
- package/runner/src/config.ts +143 -5
- package/src/agent-ref.ts +8 -6
- package/src/artifact-storage.ts +3 -5
- 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/bus.ts +3 -4
- package/src/cli/_shared.ts +5 -4
- package/src/cli/agent-detect.ts +6 -4
- package/src/cli/introspect.ts +3 -2
- package/src/cli/token.ts +2 -1
- package/src/cli/workspace.ts +2 -1
- package/src/config.ts +176 -1
- package/src/connectors.ts +3 -3
- 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/connection.ts +2 -1
- package/src/db/continuation-archives.ts +7 -7
- package/src/db/delivery.ts +7 -12
- 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 +26 -9
- package/src/db/messages.ts +17 -41
- 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/index.ts +6 -5
- package/src/issue-tracker/github.ts +3 -3
- package/src/maintenance/constants.ts +52 -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 +5 -4
- package/src/provider-state.ts +2 -4
- package/src/reviewer-pipeline.ts +1 -1
- package/src/routes/agent-sessions.ts +5 -4
- package/src/routes/orchestrator-proxy.ts +5 -4
- package/src/routes/workspaces.ts +5 -4
- package/src/security.ts +13 -7
- 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 +8 -11
- package/src/services/managed-running.ts +1 -1
- package/src/services/parity-harness.ts +3 -3
- package/src/services/plan-graph.ts +4 -7
- 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/upgrade.ts +7 -9
- package/src/workspace-actions.ts +1 -1
- package/src/workspace-auto-merge.ts +4 -5
- package/src/workspace-claim.ts +1 -2
- package/src/workspace-orphans.ts +2 -7
- package/src/workspace-probe.ts +5 -8
- 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/bus.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { applyCommandToRecipe } from "./recipe-runner";
|
|
|
15
15
|
import { enqueueContinuationInjectionAfterClear } from "./self-resume";
|
|
16
16
|
import { messageMatchesAgent, targetMatchesAgent } from "./agent-ref";
|
|
17
17
|
import { probeWorkspaceOnTurnEnd } from "./workspace-probe";
|
|
18
|
+
import { busStaleGraceMs } from "./config";
|
|
18
19
|
import {
|
|
19
20
|
BusProtocolError,
|
|
20
21
|
parseBusFrame,
|
|
@@ -107,11 +108,9 @@ export function getBusConnectionCount(): number {
|
|
|
107
108
|
return busConnections.size;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
export function expireStaleBusAgents(graceMs =
|
|
111
|
+
export function expireStaleBusAgents(graceMs = busStaleGraceMs()): { agentIds: string[]; orphanedTasks: Task[] } {
|
|
111
112
|
const cutoff = Date.now() - graceMs;
|
|
112
|
-
const rows = getDb()
|
|
113
|
-
.query("SELECT id FROM agents WHERE status = 'stale' AND last_seen < ? AND id NOT IN ('user', 'system')")
|
|
114
|
-
.all(cutoff) as Array<{ id: string }>;
|
|
113
|
+
const rows = getDb().query("SELECT id FROM agents WHERE status = 'stale' AND last_seen < ? AND id NOT IN ('user', 'system')").all(cutoff) as Array<{ id: string }>;
|
|
115
114
|
const orphanedTasks: Task[] = [];
|
|
116
115
|
for (const row of rows) {
|
|
117
116
|
// Bus-grace staleness across a relay restart/reconnect — the process may be alive.
|
package/src/cli/_shared.ts
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { stdin as input, stdout as output } from "node:process";
|
|
7
7
|
import { RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
8
|
+
import { cliRelayToken, cliRelayUrl } from "../config";
|
|
8
9
|
|
|
9
10
|
export async function apiRequest(method: string, path: string, body?: unknown): Promise<unknown> {
|
|
10
|
-
const baseUrl =
|
|
11
|
+
const baseUrl = cliRelayUrl();
|
|
11
12
|
const headers: Record<string, string> = {};
|
|
12
|
-
const token =
|
|
13
|
+
const token = cliRelayToken();
|
|
13
14
|
if (token) headers[RELAY_TOKEN_HEADER] = token;
|
|
14
15
|
if (body !== undefined) headers["Content-Type"] = "application/json";
|
|
15
16
|
const response = await fetch(new URL(path, baseUrl), {
|
|
@@ -27,9 +28,9 @@ export async function apiRequest(method: string, path: string, body?: unknown):
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export async function apiRawRequest(method: string, path: string, body: BodyInit, extraHeaders: Record<string, string> = {}): Promise<unknown> {
|
|
30
|
-
const baseUrl =
|
|
31
|
+
const baseUrl = cliRelayUrl();
|
|
31
32
|
const headers: Record<string, string> = { ...extraHeaders };
|
|
32
|
-
const token =
|
|
33
|
+
const token = cliRelayToken();
|
|
33
34
|
if (token) headers[RELAY_TOKEN_HEADER] = token;
|
|
34
35
|
const response = await fetch(new URL(path, baseUrl), { method, headers, body });
|
|
35
36
|
const text = await response.text();
|
package/src/cli/agent-detect.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
6
6
|
import { join, resolve } from "node:path";
|
|
7
7
|
import { apiRequest, uniqueStrings } from "./_shared";
|
|
8
|
+
import { agentIdFromEnv, codexStatePathFromEnv, contextPathFromEnv } from "../config";
|
|
8
9
|
|
|
9
10
|
export async function detectActivePairId(agentId: string): Promise<string | undefined> {
|
|
10
11
|
const pairs = await apiRequest("GET", `/api/pairs?agent=${encodeURIComponent(agentId)}&status=active`) as Array<{ id?: string }>;
|
|
@@ -12,15 +13,16 @@ export async function detectActivePairId(agentId: string): Promise<string | unde
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export async function detectAgentId(): Promise<string | undefined> {
|
|
15
|
-
const explicit =
|
|
16
|
+
const explicit = agentIdFromEnv();
|
|
16
17
|
if (explicit) return explicit;
|
|
17
18
|
|
|
18
19
|
const contextMatch = currentAgentContextId();
|
|
19
20
|
if (contextMatch) return contextMatch;
|
|
20
21
|
|
|
21
22
|
const cwd = process.cwd();
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
const explicitCodexStatePath = codexStatePathFromEnv();
|
|
24
|
+
const explicitCodexState = explicitCodexStatePath
|
|
25
|
+
? readCodexState(explicitCodexStatePath)
|
|
24
26
|
: null;
|
|
25
27
|
if (explicitCodexState?.agentId) return explicitCodexState.agentId;
|
|
26
28
|
|
|
@@ -48,7 +50,7 @@ export async function detectAgentId(): Promise<string | undefined> {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
function currentAgentContextId(): string | undefined {
|
|
51
|
-
const explicitPath =
|
|
53
|
+
const explicitPath = contextPathFromEnv();
|
|
52
54
|
if (explicitPath) {
|
|
53
55
|
const explicit = readAgentContext(explicitPath);
|
|
54
56
|
if (explicit?.agentId && contextMatchesCurrentProcess(explicit)) return explicit.agentId;
|
package/src/cli/introspect.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// end-of-session self-view as a bounded, structured Insights observation (#185).
|
|
3
3
|
import { apiRequest, readStdin } from "./_shared";
|
|
4
4
|
import { detectAgentId } from "./agent-detect";
|
|
5
|
+
import { projectFromEnv, providerSessionIdFromEnv } from "../config";
|
|
5
6
|
|
|
6
7
|
// Insights #185: capture the agent's end-of-session self-view as a bounded, structured
|
|
7
8
|
// artifact (epic #183, docs/self-improvement.md). Manual rail in v0 — the agent or
|
|
@@ -61,8 +62,8 @@ export async function handleIntrospectCommand(args: string[]): Promise<void> {
|
|
|
61
62
|
if (!from) throw new Error("Could not detect current Agent Relay ID. Pass --from AGENT_ID or set AGENT_RELAY_ID.");
|
|
62
63
|
|
|
63
64
|
const observation = await apiRequest("POST", "/api/insights/observations", {
|
|
64
|
-
sessionId: sessionId ||
|
|
65
|
-
project: project ||
|
|
65
|
+
sessionId: sessionId || providerSessionIdFromEnv() || `manual-${from}`,
|
|
66
|
+
project: project || projectFromEnv() || process.cwd().split("/").filter(Boolean).at(-1) || process.cwd(),
|
|
66
67
|
agentId: from,
|
|
67
68
|
signal: "introspection",
|
|
68
69
|
source: "agent",
|
package/src/cli/token.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Token commands — auto-split from cli.ts (#294). create/list/revoke/verify for
|
|
2
2
|
// component JWTs.
|
|
3
3
|
import { apiRequest, splitTagArgs } from "./_shared";
|
|
4
|
+
import { cliRelayToken } from "../config";
|
|
4
5
|
|
|
5
6
|
export async function handleTokenCommand(args: string[]): Promise<void> {
|
|
6
7
|
const action = args[0] ?? "list";
|
|
@@ -45,7 +46,7 @@ export async function handleTokenCommand(args: string[]): Promise<void> {
|
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
if (action === "verify") {
|
|
48
|
-
const token = rest.find((arg) => !arg.startsWith("--")) ??
|
|
49
|
+
const token = rest.find((arg) => !arg.startsWith("--")) ?? cliRelayToken();
|
|
49
50
|
if (!token) throw new Error("Usage: agent-relay token verify TOKEN");
|
|
50
51
|
const payload = decodeJwtPayload(token);
|
|
51
52
|
if (!payload) throw new Error("not a component JWT");
|
package/src/cli/workspace.ts
CHANGED
|
@@ -7,13 +7,14 @@ import { describeWorkspacePhase, readyContract, type WorkspacePhaseView } from "
|
|
|
7
7
|
import type { WorkspaceDepsRefreshResult } from "agent-relay-sdk";
|
|
8
8
|
import { AUTO_MERGE_POLICIES } from "../workspace-merge";
|
|
9
9
|
import type { WorkspaceAutoMergePolicy } from "../types";
|
|
10
|
+
import { workspaceJsonFromEnv } from "../config";
|
|
10
11
|
|
|
11
12
|
export const WORKSPACE_USAGE = "Usage: agent-relay workspace <status|diagnostics|ready|land|claim|release|list|cleanup-stale|deps> [--id ID] [--strategy ...] [--auto-merge on-green|on-approval|manual] [--reviewer HANDLE] [--purpose TEXT] [--repo PATH] [--wait] [--timeout SECONDS] [--check] [--execute] [--json]";
|
|
12
13
|
|
|
13
14
|
// The agent's own isolated-workspace id, published in AGENT_RELAY_WORKSPACE_JSON
|
|
14
15
|
// by the orchestrator at spawn. Undefined for shared-workspace / non-managed agents.
|
|
15
16
|
function currentWorkspaceId(): string | undefined {
|
|
16
|
-
const json =
|
|
17
|
+
const json = workspaceJsonFromEnv();
|
|
17
18
|
if (!json) return undefined;
|
|
18
19
|
try {
|
|
19
20
|
const parsed = JSON.parse(json) as { id?: string };
|
package/src/config.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Shared runtime constants. Import from here rather than redefining.
|
|
2
2
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
4
5
|
import { join, dirname } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { DEFAULT_RELAY_URL, RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
6
8
|
import { CONTRACT_VERSIONS } from "./contracts";
|
|
7
9
|
|
|
8
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -19,6 +21,31 @@ function envPositiveInt(name: string, fallback: number): number {
|
|
|
19
21
|
return Math.floor(parsed);
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
function envNumberOrDefault(name: string, fallback: number): number {
|
|
25
|
+
return Number(process.env[name]) || fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function envNonNegativeNumber(name: string, fallback: number): number {
|
|
29
|
+
const parsed = Number(process.env[name]);
|
|
30
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function envSafePositiveInteger(name: string, fallback: number): number {
|
|
34
|
+
const raw = process.env[name];
|
|
35
|
+
if (!raw) return fallback;
|
|
36
|
+
const parsed = Number(raw);
|
|
37
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function envTrimmed(name: string): string {
|
|
41
|
+
return (process.env[name] || "").trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function envTrimmedOrUndefined(name: string): string | undefined {
|
|
45
|
+
const value = process.env[name]?.trim();
|
|
46
|
+
return value || undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
22
49
|
export const STALE_TTL_MS = envPositiveInt("STALE_TTL_MS", 120_000); // 2min without heartbeat → offline
|
|
23
50
|
// #451 — default TTL for fire-and-forget FAN-OUT messages (broadcast/tag/cap/label/team) that the
|
|
24
51
|
// sender didn't bound. A stale fan-out push must not wake an agent that matches the target hours
|
|
@@ -42,12 +69,160 @@ export const MAIN_MERGE_PAUSE_TTL_MS = envPositiveInt("AGENT_RELAY_MAIN_MERGE_PA
|
|
|
42
69
|
export const MAX_BODY_BYTES = 64 * 1024;
|
|
43
70
|
export const INTEGRATION_RATE_LIMIT_PER_MINUTE = envPositiveInt("AGENT_RELAY_INTEGRATION_RATE_LIMIT_PER_MINUTE", 120);
|
|
44
71
|
|
|
45
|
-
export const
|
|
72
|
+
export const AUTOMATION_INTERVAL_MS = envNumberOrDefault("AGENT_RELAY_AUTOMATION_INTERVAL_MS", 30 * 1000);
|
|
73
|
+
export const IDLE_TIMEOUT_SECONDS = envNumberOrDefault("AGENT_RELAY_IDLE_TIMEOUT_SECONDS", 255);
|
|
74
|
+
export const LOG_REQUESTS = process.env.AGENT_RELAY_LOG_REQUESTS === "1";
|
|
75
|
+
|
|
76
|
+
const AUTH_TOKEN = process.env.AGENT_RELAY_TOKEN || "";
|
|
46
77
|
export const CORS_ORIGINS = (process.env.AGENT_RELAY_CORS_ORIGINS || "")
|
|
47
78
|
.split(",")
|
|
48
79
|
.map((origin) => origin.trim())
|
|
49
80
|
.filter(Boolean);
|
|
50
81
|
|
|
82
|
+
export function relayToken(): string {
|
|
83
|
+
return process.env.AGENT_RELAY_TOKEN ?? AUTH_TOKEN;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function relayAuthHeaders(base: Record<string, string> = {}): Record<string, string> {
|
|
87
|
+
const token = relayToken();
|
|
88
|
+
return token ? { ...base, [RELAY_TOKEN_HEADER]: token } : base;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function allowUnauthenticated(): boolean {
|
|
92
|
+
return process.env.AGENT_RELAY_ALLOW_UNAUTH === "1";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function tokenSecretFromEnv(): string | undefined {
|
|
96
|
+
return process.env.AGENT_RELAY_TOKEN_SECRET;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function corsOriginsFromEnv(): string[] | undefined {
|
|
100
|
+
const raw = process.env.AGENT_RELAY_CORS_ORIGINS;
|
|
101
|
+
if (raw === undefined) return undefined;
|
|
102
|
+
return raw.split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function connectorsDirFromEnv(): string | undefined {
|
|
106
|
+
return process.env.AGENT_RELAY_CONNECTORS_DIR;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function connectorActionTimeoutMs(fallback: number): number {
|
|
110
|
+
const raw = Number(process.env.AGENT_RELAY_CONNECTOR_ACTION_TIMEOUT_MS);
|
|
111
|
+
return Number.isFinite(raw) && raw > 0 ? raw : fallback;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function rateLimitFallbackMs(): number {
|
|
115
|
+
return envNumberOrDefault("AGENT_RELAY_RATE_LIMIT_FALLBACK_MS", 60 * 60 * 1000);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function artifactsDir(): string {
|
|
119
|
+
return process.env.AGENT_RELAY_ARTIFACTS_DIR || "~/.agent-relay/artifacts";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function maxArtifactBytesFromEnv(fallback: number): number {
|
|
123
|
+
return envSafePositiveInteger("AGENT_RELAY_MAX_ARTIFACT_BYTES", fallback);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const OUTBOX_RETENTION_MS = envNumberOrDefault("AGENT_RELAY_OUTBOX_RETENTION_MS", 60 * 60 * 1000);
|
|
127
|
+
export const TOKEN_RECORD_RETENTION_SECONDS = envNumberOrDefault("AGENT_RELAY_TOKEN_RECORD_RETENTION_SECONDS", 7 * 24 * 60 * 60);
|
|
128
|
+
export const CONFLICT_SCAN_INTERVAL_MS = envNumberOrDefault("AGENT_RELAY_CONFLICT_SCAN_INTERVAL_MS", 10 * 60 * 1000);
|
|
129
|
+
export const WORKSPACE_RETENTION_MS = envNumberOrDefault("AGENT_RELAY_WORKSPACE_RETENTION_MS", DAY_MS);
|
|
130
|
+
export const DB_MAINTENANCE_INTERVAL_MS = envNumberOrDefault("AGENT_RELAY_DB_MAINTENANCE_INTERVAL_MS", DAY_MS);
|
|
131
|
+
export const DB_VACUUM_EVERY = envNumberOrDefault("AGENT_RELAY_DB_VACUUM_EVERY", 7);
|
|
132
|
+
export const WORKSPACE_REVIEW_TTL_MS = envNumberOrDefault("AGENT_RELAY_WORKSPACE_REVIEW_TTL_MS", 3 * DAY_MS);
|
|
133
|
+
export const WORKSPACE_GC_INTERVAL_MS = envNumberOrDefault("AGENT_RELAY_WORKSPACE_GC_INTERVAL_MS", 60 * 60 * 1000);
|
|
134
|
+
export const WORKSPACE_ORPHAN_REAPER_INTERVAL_MS = envNumberOrDefault("AGENT_RELAY_WORKSPACE_ORPHAN_REAPER_INTERVAL_MS", 30 * 60 * 1000);
|
|
135
|
+
export const WORKSPACE_AUTO_MERGE_INTERVAL_MS = envNumberOrDefault("AGENT_RELAY_WORKSPACE_AUTO_MERGE_INTERVAL_MS", CONFLICT_SCAN_INTERVAL_MS);
|
|
136
|
+
export const ORPHAN_REAPER_INTERVAL_MS = envNumberOrDefault("AGENT_RELAY_ORPHAN_REAPER_INTERVAL_MS", 5 * 60 * 1000);
|
|
137
|
+
export const STEWARD_WAKE_COOLDOWN_MS = envNumberOrDefault("AGENT_RELAY_STEWARD_WAKE_COOLDOWN_MS", 10 * 60 * 1000);
|
|
138
|
+
export const UNLANDED_FLAG_COOLDOWN_MS = envNumberOrDefault("AGENT_RELAY_ORPHAN_FLAG_COOLDOWN_MS", 6 * 60 * 60 * 1000);
|
|
139
|
+
export const STEWARD_CLAIM_TTL_MS = envNumberOrDefault("AGENT_RELAY_WORKSPACE_CLAIM_TTL_MS", 15 * 60_000);
|
|
140
|
+
export const DB_SLOW_QUERY_MS = envNumberOrDefault("AGENT_RELAY_DB_SLOW_MS", 0);
|
|
141
|
+
|
|
142
|
+
export function stewardEscalationMs(): number {
|
|
143
|
+
return envNumberOrDefault("AGENT_RELAY_WORKSPACE_STEWARD_ESCALATION_MS", 60 * 60 * 1000);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function stewardFallbackTarget(): string {
|
|
147
|
+
return envTrimmed("AGENT_RELAY_WORKSPACE_STEWARD_FALLBACK");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function envMsOrDefault(name: string, fallback: number): number {
|
|
151
|
+
return envNonNegativeNumber(name, fallback);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function orphanGraceMs(): number {
|
|
155
|
+
return envMsOrDefault("AGENT_RELAY_ORPHAN_GRACE_MS", 30 * 60 * 1000);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function orphanReapCooldownMs(): number {
|
|
159
|
+
return envMsOrDefault("AGENT_RELAY_ORPHAN_REAP_COOLDOWN_MS", 5 * 60 * 1000);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function orphanReapEnabled(): boolean {
|
|
163
|
+
return process.env.AGENT_RELAY_ORPHAN_REAP !== "0";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function orphanWorktreeReapEnabled(): boolean {
|
|
167
|
+
return process.env.AGENT_RELAY_ORPHAN_WORKTREE_REAP !== "0";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function workspaceAutoMergeEnabled(): boolean {
|
|
171
|
+
return process.env.AGENT_RELAY_WORKSPACE_AUTO_MERGE !== "0";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function continuationArchiveFtsEnabled(): boolean {
|
|
175
|
+
return process.env.AGENT_RELAY_DISABLE_CONTINUATION_ARCHIVE_FTS !== "1";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function busStaleGraceMs(): number {
|
|
179
|
+
return envNumberOrDefault("AGENT_RELAY_STALE_GRACE_MS", 120_000);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function defaultIssueRepo(): string | undefined {
|
|
183
|
+
return process.env.AGENT_RELAY_DEFAULT_ISSUE_REPO || process.env.GITHUB_REPOSITORY || undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function cliRelayUrl(): string {
|
|
187
|
+
return process.env.AGENT_RELAY_URL || DEFAULT_RELAY_URL;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function cliRelayToken(): string | undefined {
|
|
191
|
+
return process.env.AGENT_RELAY_TOKEN;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function workspaceJsonFromEnv(): string | undefined {
|
|
195
|
+
return process.env.AGENT_RELAY_WORKSPACE_JSON;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function providerSessionIdFromEnv(): string | undefined {
|
|
199
|
+
return process.env.AGENT_RELAY_PROVIDER_SESSION_ID;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function projectFromEnv(): string | undefined {
|
|
203
|
+
return process.env.AGENT_RELAY_PROJECT;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function agentIdFromEnv(): string | undefined {
|
|
207
|
+
return process.env.AGENT_RELAY_ID;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function codexStatePathFromEnv(): string | undefined {
|
|
211
|
+
return process.env.AGENT_RELAY_CODEX_STATE_PATH;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function contextPathFromEnv(): string | undefined {
|
|
215
|
+
return process.env.AGENT_RELAY_CONTEXT_PATH;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function localOrchestratorIdFromEnv(): string | undefined {
|
|
219
|
+
return envTrimmedOrUndefined("AGENT_RELAY_ORCHESTRATOR_ID");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function providerConfigHomeFromEnv(): string {
|
|
223
|
+
return process.env.AGENT_RELAY_HOME || join(process.env.HOME || ".", ".agent-relay");
|
|
224
|
+
}
|
|
225
|
+
|
|
51
226
|
export type IntegrationTokenConfig = {
|
|
52
227
|
name: string;
|
|
53
228
|
token: string;
|
package/src/connectors.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ConnectorAction, ConnectorActionResult, ConnectorManifest, Connect
|
|
|
5
5
|
import { ValidationError } from "./db";
|
|
6
6
|
import { isRecord } from "agent-relay-sdk";
|
|
7
7
|
import { registerConnectorConfigSetting, validateConnectorConfig } from "./settings/modules";
|
|
8
|
+
import { connectorActionTimeoutMs as configuredConnectorActionTimeoutMs, connectorsDirFromEnv } from "./config";
|
|
8
9
|
|
|
9
10
|
const CONNECTOR_SCHEMA = "agent-relay.connector.v1";
|
|
10
11
|
const VALID_KINDS = new Set(["channel", "event", "provider", "orchestrator"]);
|
|
@@ -12,7 +13,7 @@ const VALID_ACTIONS = new Set(["install", "uninstall", "enable", "disable", "sta
|
|
|
12
13
|
const DEFAULT_ACTION_TIMEOUT_MS = 30_000;
|
|
13
14
|
|
|
14
15
|
function connectorRegistryDir(): string {
|
|
15
|
-
const configured =
|
|
16
|
+
const configured = connectorsDirFromEnv();
|
|
16
17
|
if (configured) return resolveHome(configured);
|
|
17
18
|
return join(homedir(), ".agent-relay", "connectors");
|
|
18
19
|
}
|
|
@@ -177,8 +178,7 @@ export function writeConnectorConfig(id: string, config: Record<string, unknown>
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
function connectorActionTimeoutMs(): number {
|
|
180
|
-
|
|
181
|
-
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_ACTION_TIMEOUT_MS;
|
|
181
|
+
return configuredConnectorActionTimeoutMs(DEFAULT_ACTION_TIMEOUT_MS);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
const LIFECYCLE_ACTIONS = new Set(["install", "uninstall", "start", "stop", "restart", "enable", "disable"]);
|
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
|
+
}
|