@relaycast/engine 8.1.2 → 8.2.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.
Files changed (40) hide show
  1. package/dist/auth/index.js +3 -3
  2. package/dist/auth/index.js.map +1 -1
  3. package/dist/db/migrations/0039_workspace_usage_attribution.sql +24 -0
  4. package/dist/db/migrations/0041_agent_identity_recovery.sql +40 -0
  5. package/dist/db/migrations/0041_d1_hot_path_indexes.sql +12 -0
  6. package/dist/db/schema.d.ts +515 -0
  7. package/dist/db/schema.d.ts.map +1 -1
  8. package/dist/db/schema.js +73 -1
  9. package/dist/db/schema.js.map +1 -1
  10. package/dist/engine/agent.d.ts +3 -1
  11. package/dist/engine/agent.d.ts.map +1 -1
  12. package/dist/engine/agent.js +37 -1
  13. package/dist/engine/agent.js.map +1 -1
  14. package/dist/engine/agentIdentity.d.ts +49 -0
  15. package/dist/engine/agentIdentity.d.ts.map +1 -0
  16. package/dist/engine/agentIdentity.js +158 -0
  17. package/dist/engine/agentIdentity.js.map +1 -0
  18. package/dist/engine/node.d.ts +9 -1
  19. package/dist/engine/node.d.ts.map +1 -1
  20. package/dist/engine/node.js +122 -54
  21. package/dist/engine/node.js.map +1 -1
  22. package/dist/engine/tokenRotate.d.ts +3 -4
  23. package/dist/engine/tokenRotate.d.ts.map +1 -1
  24. package/dist/engine/tokenRotate.js +4 -5
  25. package/dist/engine/tokenRotate.js.map +1 -1
  26. package/dist/engine/workspace.d.ts +36 -0
  27. package/dist/engine/workspace.d.ts.map +1 -1
  28. package/dist/engine/workspace.js +17 -1
  29. package/dist/engine/workspace.js.map +1 -1
  30. package/dist/lib/workspaceProvenance.d.ts +17 -0
  31. package/dist/lib/workspaceProvenance.d.ts.map +1 -0
  32. package/dist/lib/workspaceProvenance.js +41 -0
  33. package/dist/lib/workspaceProvenance.js.map +1 -0
  34. package/dist/routes/agent.d.ts.map +1 -1
  35. package/dist/routes/agent.js +223 -2
  36. package/dist/routes/agent.js.map +1 -1
  37. package/dist/routes/workspace.d.ts.map +1 -1
  38. package/dist/routes/workspace.js +28 -5
  39. package/dist/routes/workspace.js.map +1 -1
  40. package/package.json +3 -3
@@ -0,0 +1,49 @@
1
+ import type { getDb } from '../db/index.js';
2
+ type Db = ReturnType<typeof getDb>;
3
+ export type AgentIdentityAuthority = 'current_agent_token' | 'origin_node' | 'work_unit_proof' | 'workspace_admin';
4
+ export interface AgentIdentityTarget {
5
+ workspaceId: string;
6
+ agentId: string;
7
+ agentName: string;
8
+ }
9
+ export interface AgentIdentityAuditContext {
10
+ authority: AgentIdentityAuthority;
11
+ actor: string;
12
+ reason: string;
13
+ sessionRef?: string | null;
14
+ nodeId?: string | null;
15
+ originActor: string;
16
+ }
17
+ export declare function getRecoveryCredentialByProof(db: Db, rawProof: string): Promise<{
18
+ workspaceId: string;
19
+ agentId: string;
20
+ agentName: string;
21
+ workUnitId: string | null;
22
+ } | null>;
23
+ export declare function enrollRecoveryCredential(db: Db, target: Pick<AgentIdentityTarget, 'workspaceId' | 'agentId'>, input: {
24
+ verifierHash: string;
25
+ proofKind?: 'work_unit';
26
+ workUnitId?: string | null;
27
+ }): Promise<void>;
28
+ export declare function rotateAgentIdentity(db: Db, target: AgentIdentityTarget, audit: AgentIdentityAuditContext, action?: 'recover' | 'takeover', options?: {
29
+ requireAtomic?: boolean;
30
+ alreadyAtomic?: boolean;
31
+ }): Promise<{
32
+ agent_id: string;
33
+ name: string;
34
+ token: string;
35
+ audit_id: string;
36
+ }>;
37
+ export declare function takeOverAgentIdentity(db: Db, target: AgentIdentityTarget, audit: Omit<AgentIdentityAuditContext, 'authority'>): Promise<{
38
+ agent_id: string;
39
+ name: string;
40
+ token: string;
41
+ audit_id: string;
42
+ }>;
43
+ export declare function revokeAgentIdentityTokens(db: Db, target: AgentIdentityTarget, audit: Omit<AgentIdentityAuditContext, 'authority'>): Promise<{
44
+ agent_id: string;
45
+ name: string;
46
+ audit_id: string;
47
+ }>;
48
+ export {};
49
+ //# sourceMappingURL=agentIdentity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentIdentity.d.ts","sourceRoot":"","sources":["../../src/engine/agentIdentity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAS5C,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAEnC,MAAM,MAAM,sBAAsB,GAC9B,qBAAqB,GACrB,aAAa,GACb,iBAAiB,GACjB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,sBAAsB,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AA6CD,wBAAsB,4BAA4B,CAChD,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IACT,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,IAAI,CAAC,CAgBR;AAED,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,IAAI,CAAC,mBAAmB,EAAE,aAAa,GAAG,SAAS,CAAC,EAC5D,KAAK,EAAE;IACL,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,GACA,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,yBAAyB,EAChC,MAAM,GAAE,SAAS,GAAG,UAAsB,EAC1C,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACjE,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAiD9E;AAED,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,IAAI,CAAC,yBAAyB,EAAE,WAAW,CAAC,GAClD,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAK9E;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,IAAI,CAAC,yBAAyB,EAAE,WAAW,CAAC,GAClD,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoC/D"}
@@ -0,0 +1,158 @@
1
+ import { and, eq, ne, sql } from 'drizzle-orm';
2
+ import { agentIdentityAudit, agentRecoveryCredentials, agents } from '../db/schema.js';
3
+ import { randomHex, sha256Hex } from '../lib/crypto.js';
4
+ import { codedError } from '../lib/httpError.js';
5
+ import { runAtomicWrites } from '../ports/database.js';
6
+ import { generateId } from './snowflake.js';
7
+ import { AGENT_TOKEN_GRACE_MS } from './tokenRotate.js';
8
+ import { RELEASED_AGENT_STATUS } from './agent.js';
9
+ function targetPredicate(target) {
10
+ return and(eq(agents.workspaceId, target.workspaceId), eq(agents.id, target.agentId), eq(agents.name, target.agentName), ne(agents.status, RELEASED_AGENT_STATUS));
11
+ }
12
+ function auditInsertAfterTokenChange(writeDb, target, tokenHash, row) {
13
+ // The SELECT sees the token update earlier in the same transaction/batch.
14
+ // If the target predicate matched no live agent, it produces no row, so D1
15
+ // cannot commit a successful-looking audit entry for a failed mutation.
16
+ return writeDb.insert(agentIdentityAudit).select(writeDb
17
+ .select({
18
+ id: sql `${row.id}`.as('id'),
19
+ workspaceId: sql `${row.workspaceId}`.as('workspace_id'),
20
+ agentId: sql `${row.agentId}`.as('agent_id'),
21
+ agentName: sql `${row.agentName}`.as('agent_name'),
22
+ action: sql `${row.action}`.as('action'),
23
+ authority: sql `${row.authority}`.as('authority'),
24
+ actor: sql `${row.actor}`.as('actor'),
25
+ reason: sql `${row.reason}`.as('reason'),
26
+ sessionRef: sql `${row.sessionRef ?? null}`.as('session_ref'),
27
+ nodeId: sql `${row.nodeId ?? null}`.as('node_id'),
28
+ originActor: sql `${row.originActor}`.as('origin_actor'),
29
+ createdAt: sql `unixepoch()`.as('created_at'),
30
+ })
31
+ .from(agents)
32
+ .where(and(targetPredicate(target), eq(agents.tokenHash, tokenHash))));
33
+ }
34
+ function firstReturnedRow(result) {
35
+ return Array.isArray(result) ? result[0] : undefined;
36
+ }
37
+ export async function getRecoveryCredentialByProof(db, rawProof) {
38
+ const verifierHash = await sha256Hex(rawProof);
39
+ const [row] = await db
40
+ .select({
41
+ workspaceId: agentRecoveryCredentials.workspaceId,
42
+ agentId: agentRecoveryCredentials.agentId,
43
+ agentName: agents.name,
44
+ workUnitId: agentRecoveryCredentials.workUnitId,
45
+ })
46
+ .from(agentRecoveryCredentials)
47
+ .innerJoin(agents, and(eq(agents.workspaceId, agentRecoveryCredentials.workspaceId), eq(agents.id, agentRecoveryCredentials.agentId)))
48
+ .where(eq(agentRecoveryCredentials.verifierHash, verifierHash));
49
+ return row ?? null;
50
+ }
51
+ export async function enrollRecoveryCredential(db, target, input) {
52
+ const now = new Date();
53
+ await db
54
+ .insert(agentRecoveryCredentials)
55
+ .values({
56
+ id: `arc_${generateId()}`,
57
+ workspaceId: target.workspaceId,
58
+ agentId: target.agentId,
59
+ proofKind: input.proofKind ?? 'work_unit',
60
+ verifierHash: input.verifierHash,
61
+ workUnitId: input.workUnitId ?? null,
62
+ createdAt: now,
63
+ updatedAt: now,
64
+ })
65
+ .onConflictDoUpdate({
66
+ target: [agentRecoveryCredentials.workspaceId, agentRecoveryCredentials.agentId],
67
+ set: {
68
+ proofKind: input.proofKind ?? 'work_unit',
69
+ verifierHash: input.verifierHash,
70
+ workUnitId: input.workUnitId ?? null,
71
+ updatedAt: now,
72
+ },
73
+ });
74
+ }
75
+ export async function rotateAgentIdentity(db, target, audit, action = 'recover', options = {}) {
76
+ const token = `at_live_${randomHex(16)}`;
77
+ const tokenHash = await sha256Hex(token);
78
+ const auditId = `aid_${generateId()}`;
79
+ const graceExpiresAtSeconds = Math.floor((Date.now() + AGENT_TOKEN_GRACE_MS) / 1000);
80
+ const buildWrites = (writeDb) => [
81
+ writeDb
82
+ .update(agents)
83
+ .set({
84
+ previousTokenHash: sql `${agents.tokenHash}`,
85
+ previousTokenExpiresAt: sql `${graceExpiresAtSeconds}`,
86
+ tokenHash,
87
+ })
88
+ .where(targetPredicate(target))
89
+ .returning({ id: agents.id }),
90
+ auditInsertAfterTokenChange(writeDb, target, tokenHash, {
91
+ id: auditId,
92
+ workspaceId: target.workspaceId,
93
+ agentId: target.agentId,
94
+ agentName: target.agentName,
95
+ action,
96
+ authority: audit.authority,
97
+ actor: audit.actor,
98
+ reason: audit.reason,
99
+ sessionRef: audit.sessionRef ?? null,
100
+ nodeId: audit.nodeId ?? null,
101
+ originActor: audit.originActor,
102
+ }),
103
+ ];
104
+ let results;
105
+ if (options.alreadyAtomic) {
106
+ results = [];
107
+ for (const write of buildWrites(db))
108
+ results.push(await write);
109
+ }
110
+ else {
111
+ results = await runAtomicWrites(db, buildWrites, {
112
+ requireAtomic: options.requireAtomic ?? true,
113
+ });
114
+ }
115
+ if (!firstReturnedRow(results[0])) {
116
+ throw codedError(`Agent "${target.agentName}" changed before identity recovery completed`, 'agent_identity_conflict', 409);
117
+ }
118
+ return { agent_id: target.agentId, name: target.agentName, token, audit_id: auditId };
119
+ }
120
+ export async function takeOverAgentIdentity(db, target, audit) {
121
+ return rotateAgentIdentity(db, target, {
122
+ ...audit,
123
+ authority: 'workspace_admin',
124
+ }, 'takeover');
125
+ }
126
+ export async function revokeAgentIdentityTokens(db, target, audit) {
127
+ const replacementHash = await sha256Hex(`revoked:${target.agentId}:${randomHex(16)}`);
128
+ const auditId = `aid_${generateId()}`;
129
+ const results = await runAtomicWrites(db, (writeDb) => [
130
+ writeDb
131
+ .update(agents)
132
+ .set({
133
+ tokenHash: replacementHash,
134
+ previousTokenHash: null,
135
+ previousTokenExpiresAt: null,
136
+ })
137
+ .where(targetPredicate(target))
138
+ .returning({ id: agents.id }),
139
+ auditInsertAfterTokenChange(writeDb, target, replacementHash, {
140
+ id: auditId,
141
+ workspaceId: target.workspaceId,
142
+ agentId: target.agentId,
143
+ agentName: target.agentName,
144
+ action: 'revoke',
145
+ authority: 'workspace_admin',
146
+ actor: audit.actor,
147
+ reason: audit.reason,
148
+ sessionRef: audit.sessionRef ?? null,
149
+ nodeId: audit.nodeId ?? null,
150
+ originActor: audit.originActor,
151
+ }),
152
+ ], { requireAtomic: true });
153
+ if (!firstReturnedRow(results[0])) {
154
+ throw codedError(`Agent "${target.agentName}" changed before revocation completed`, 'agent_identity_conflict', 409);
155
+ }
156
+ return { agent_id: target.agentId, name: target.agentName, audit_id: auditId };
157
+ }
158
+ //# sourceMappingURL=agentIdentity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentIdentity.js","sourceRoot":"","sources":["../../src/engine/agentIdentity.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { FleetAgentRegisterMessage, FleetCapabilityAcceptance, FleetInventoryAgent, FleetNodeHeartbeatMessage, FleetNodeRegisterMessage, FleetProviderIdentity, FleetCapability, AgentRegisterReplyData } from '@relaycast/types';
1
+ import type { FleetAgentRecoverMessage, FleetAgentRegisterMessage, FleetCapabilityAcceptance, FleetInventoryAgent, FleetNodeHeartbeatMessage, FleetNodeRegisterMessage, FleetProviderIdentity, FleetCapability, AgentRegisterReplyData } from '@relaycast/types';
2
2
  import type { getDb } from '../db/index.js';
3
3
  import { agents, nodes } from '../db/schema.js';
4
4
  import type { EngineDb } from '../ports/database.js';
@@ -328,6 +328,14 @@ export declare function listNodeAgents(db: Db, workspaceId: string, nodeName: st
328
328
  export declare function registerAgentViaNode(db: Db, workspaceId: string, nodeId: string, providerName: string, message: FleetAgentRegisterMessage, options?: {
329
329
  deliveryCursorSupported?: boolean;
330
330
  }): Promise<AgentRegisterReplyData>;
331
+ /**
332
+ * Recover an existing agent using the authenticated node as the authority.
333
+ * The immutable id and server-owned `origin_node_id` must both match; name,
334
+ * presence, silence, and the synthetic direct-node convention grant nothing.
335
+ */
336
+ export declare function recoverAgentViaNode(db: Db, workspaceId: string, nodeId: string, providerName: string, message: FleetAgentRecoverMessage, options?: {
337
+ deliveryCursorSupported?: boolean;
338
+ }): Promise<AgentRegisterReplyData>;
331
339
  export declare function deregisterAgentViaNode(db: Db, workspaceId: string, nodeId: string, message: {
332
340
  agent_id?: string;
333
341
  name?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/engine/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/engine/node.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { and, eq, inArray, lt, ne, or, sql } from 'drizzle-orm';
1
+ import { and, eq, inArray, ne, or, sql } from 'drizzle-orm';
2
2
  import { FLEET_DELIVERY_CURSOR_CAPABILITY, parseFleetBrokerToRelaycastMessage, } from '@relaycast/types';
3
3
  import { actionInvocations, agents, agentNodeBindings, channelMembers, channels, nodeProviders, nodes } from '../db/schema.js';
4
4
  import { randomHex, sha256Hex } from '../lib/crypto.js';
@@ -6,7 +6,8 @@ import { codedError } from '../lib/httpError.js';
6
6
  import { runAtomic } from '../ports/database.js';
7
7
  import { isProviderAgentDeliveryReady } from '../ports/realtime.js';
8
8
  import { generateId } from './snowflake.js';
9
- import { AGENT_RECLAIM_GRACE_MS, assertRegistrableAgentName } from './agent.js';
9
+ import { assertRegistrableAgentName } from './agent.js';
10
+ import { rotateAgentIdentity } from './agentIdentity.js';
10
11
  import { isNodeLive, nodeHasCapability } from './placement.js';
11
12
  import { DEFAULT_PROVIDER_NAME, capabilityKind, heartbeatProvider, markProviderOffline, materializeProviderActions, recomputeNodeAggregate, removeProvider, upsertProvider, } from './nodeProvider.js';
12
13
  import { serializeNodeOp } from './nodeLock.js';
@@ -914,11 +915,6 @@ export async function registerAgentViaNode(db, workspaceId, nodeId, providerName
914
915
  const token = `at_live_${randomHex(16)}`;
915
916
  const tokenHash = await sha256Hex(token);
916
917
  const now = new Date().toISOString();
917
- const fleetMetadata = sql `json_object(
918
- 'node_id', ${nodeId},
919
- 'invocation_id', ${message.invocation_id ?? null},
920
- 'registered_at', ${now}
921
- )`;
922
918
  const [result] = await tx
923
919
  .insert(agents)
924
920
  .values({
@@ -938,55 +934,10 @@ export async function registerAgentViaNode(db, workspaceId, nodeId, providerName
938
934
  resumable: message.resumable ?? false,
939
935
  sessionRef: message.session_ref ?? null,
940
936
  })
941
- .onConflictDoUpdate({
942
- target: [agents.workspaceId, agents.name],
943
- set: {
944
- tokenHash,
945
- status: 'active',
946
- lastSeen: new Date(),
947
- metadata: sql `json_patch(COALESCE(${agents.metadata}, '{}'), ${fleetMetadata})`,
948
- locationType: 'via_node',
949
- locationNodeId: nodeId,
950
- providerName,
951
- originNodeId: sql `COALESCE(${agents.originNodeId}, ${nodeId})`,
952
- resumable: message.resumable ?? false,
953
- sessionRef: message.session_ref ?? null,
954
- },
955
- // Who may take this name and be issued a token for it.
956
- //
957
- // The first disjunct gates on OBSERVED SILENCE (`last_seen`), not on
958
- // the `status` column. Those are not the same question and must not
959
- // share a field. `status` is maintained by `sweepStaleAgents`, which
960
- // runs on every roster read — so while identity was gated on it, an
961
- // `agent list` flipped records to 'offline' and thereby moved them
962
- // from "reclaimable only by their own node" to "reclaimable by any
963
- // node, on name alone, with a `token_hash` overwrite". A read must
964
- // never widen who may claim an identity.
965
- //
966
- // Precisely: the sweep's status update cannot affect this gate at all
967
- // any more. The sweep does also clamp a FUTURE `last_seen` back to the
968
- // server clock, which is a write — but it can only move the reclaim
969
- // moment later-or-equal relative to that bogus timestamp, never make a
970
- // row claimable now, since the clamped value is `now` and this
971
- // predicate needs `now - AGENT_RECLAIM_GRACE_MS`. That clamp exists so
972
- // a client with a skewed clock cannot make its name permanently
973
- // unreclaimable; it is deliberate, and it is the only path by which a
974
- // read touches this column.
975
- //
976
- // The grace window is far longer than the presence TTL: an agent goes
977
- // 'offline' on the roster after 5 minutes of silence, but its name is
978
- // not reclaimable by a stranger until AGENT_RECLAIM_GRACE_MS. Between
979
- // those two points the agent reads as away and its identity is still
980
- // its own.
981
- //
982
- // The second disjunct is unchanged: the agent's own node may always
983
- // re-register it, so a node restart or reconnect is never blocked by
984
- // the grace window.
985
- setWhere: or(lt(agents.lastSeen, new Date(Date.now() - AGENT_RECLAIM_GRACE_MS)), and(eq(agents.locationType, 'via_node'), or(eq(agents.locationNodeId, nodeId), sql `${agents.locationNodeId} = 'node_direct_' || ${agents.id}`))),
986
- })
937
+ .onConflictDoNothing({ target: [agents.workspaceId, agents.name] })
987
938
  .returning();
988
939
  if (!result) {
989
- throw codedError(`Agent "${message.name}" already has an active location`, 'agent_location_conflict', 409);
940
+ throw codedError(`Agent "${message.name}" already exists; use agent.recover with proof of the immutable id`, 'agent_already_exists', 409);
990
941
  }
991
942
  await autoJoinGeneral(tx, workspaceId, result.id);
992
943
  const activeNodeIds = await activeBindingNodeIdsForAgent(tx, workspaceId, result.id);
@@ -1017,6 +968,108 @@ export async function registerAgentViaNode(db, workspaceId, nodeId, providerName
1017
968
  };
1018
969
  });
1019
970
  }
971
+ /**
972
+ * Recover an existing agent using the authenticated node as the authority.
973
+ * The immutable id and server-owned `origin_node_id` must both match; name,
974
+ * presence, silence, and the synthetic direct-node convention grant nothing.
975
+ */
976
+ export async function recoverAgentViaNode(db, workspaceId, nodeId, providerName, message, options = {}) {
977
+ assertRegistrableAgentName(message.name);
978
+ const hasInteractiveTransaction = typeof db.withTransaction === 'function';
979
+ return runAtomic(db, async (tx) => {
980
+ const [node] = await tx
981
+ .select()
982
+ .from(nodes)
983
+ .where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.id, nodeId)));
984
+ if (!node)
985
+ throw codedError(`Node "${nodeId}" not found`, 'node_not_found', 404);
986
+ const [provider] = await tx
987
+ .select({ capabilities: nodeProviders.capabilities })
988
+ .from(nodeProviders)
989
+ .where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodeId), eq(nodeProviders.name, providerName)));
990
+ const cursorHandshake = (options.deliveryCursorSupported ?? true) && (provider?.capabilities?.some((capability) => capability.name === FLEET_DELIVERY_CURSOR_CAPABILITY) ?? false);
991
+ const [target] = await tx
992
+ .select()
993
+ .from(agents)
994
+ .where(and(eq(agents.workspaceId, workspaceId), eq(agents.name, message.name)));
995
+ if (!target) {
996
+ throw codedError(`Agent "${message.name}" not found`, 'agent_not_found', 404);
997
+ }
998
+ if (target.id !== message.expected_agent_id || target.originNodeId !== nodeId) {
999
+ throw codedError(`Node "${nodeId}" does not own expected agent "${message.expected_agent_id}"`, 'agent_recovery_not_authorized', 403);
1000
+ }
1001
+ const activeNodeIds = await activeBindingNodeIdsForAgent(tx, workspaceId, target.id);
1002
+ const targetWasActive = activeNodeIds.includes(nodeId);
1003
+ let reservedTargetSlot = false;
1004
+ // D1 has no interactive transaction, so capacity must be acquired before
1005
+ // any agent/location mutation. A full node now fails with the incumbent
1006
+ // row and bindings untouched; a later failure releases this reservation.
1007
+ if (!targetWasActive) {
1008
+ await reserveNodeAgentSlot(tx, workspaceId, node);
1009
+ reservedTargetSlot = true;
1010
+ }
1011
+ try {
1012
+ const now = new Date().toISOString();
1013
+ const fleetMetadata = sql `json_object(
1014
+ 'node_id', ${nodeId},
1015
+ 'invocation_id', ${message.invocation_id ?? null},
1016
+ 'registered_at', ${now}
1017
+ )`;
1018
+ const [result] = await tx
1019
+ .update(agents)
1020
+ .set({
1021
+ status: 'active',
1022
+ lastSeen: new Date(),
1023
+ metadata: sql `json_patch(COALESCE(${agents.metadata}, '{}'), ${fleetMetadata})`,
1024
+ locationType: 'via_node',
1025
+ locationNodeId: nodeId,
1026
+ providerName,
1027
+ resumable: message.resumable ?? false,
1028
+ sessionRef: message.session_ref ?? null,
1029
+ })
1030
+ .where(and(eq(agents.workspaceId, workspaceId), eq(agents.id, message.expected_agent_id), eq(agents.name, message.name), eq(agents.originNodeId, nodeId)))
1031
+ .returning();
1032
+ if (!result) {
1033
+ throw codedError(`Agent "${message.name}" changed during node recovery`, 'agent_identity_conflict', 409);
1034
+ }
1035
+ await autoJoinGeneral(tx, workspaceId, result.id);
1036
+ await upsertAgentNodeBinding(tx, workspaceId, result, nodeId, {
1037
+ sessionRef: message.session_ref ?? null,
1038
+ deactivateExisting: true,
1039
+ });
1040
+ await releaseNodeAgentSlots(tx, workspaceId, activeNodeIds.filter((activeNodeId) => activeNodeId !== nodeId));
1041
+ // Rotate only after every fallible routing/capacity mutation. On Node the
1042
+ // surrounding transaction still makes the whole recovery atomic; on D1
1043
+ // the rotation+audit pair remains one atomic batch.
1044
+ const rotated = await rotateAgentIdentity(tx, {
1045
+ workspaceId,
1046
+ agentId: target.id,
1047
+ agentName: target.name,
1048
+ }, {
1049
+ authority: 'origin_node',
1050
+ actor: `node:${node.name}`,
1051
+ reason: 'explicit node identity recovery',
1052
+ sessionRef: message.session_ref ?? target.sessionRef,
1053
+ nodeId,
1054
+ originActor: 'node-control/agent.recover',
1055
+ }, 'recover', {
1056
+ requireAtomic: !hasInteractiveTransaction,
1057
+ alreadyAtomic: hasInteractiveTransaction,
1058
+ });
1059
+ return {
1060
+ agent_id: result.id,
1061
+ name: result.name,
1062
+ token: rotated.token,
1063
+ ...(cursorHandshake ? { delivery_ack_seq: result.deliveryAckSeq } : {}),
1064
+ };
1065
+ }
1066
+ catch (err) {
1067
+ if (reservedTargetSlot)
1068
+ await releaseNodeAgentSlots(tx, workspaceId, [nodeId]);
1069
+ throw err;
1070
+ }
1071
+ });
1072
+ }
1020
1073
  export async function deregisterAgentViaNode(db, workspaceId, nodeId, message, deps) {
1021
1074
  const conditions = [
1022
1075
  eq(agents.workspaceId, workspaceId),
@@ -1445,6 +1498,21 @@ export async function handleNodeControlMessage(args) {
1445
1498
  await deliverPendingToNode(args.db, args.registry, args.workspaceId, args.nodeId, { providerName: frameProviderName, agentIds: [registered.agent_id] });
1446
1499
  return;
1447
1500
  }
1501
+ case 'agent.recover': {
1502
+ const recovered = await recoverAgentViaNode(args.db, args.workspaceId, args.nodeId, frameProviderName, message, { deliveryCursorSupported: supportsProviderDeliveryReadiness(args.registry) });
1503
+ const replySent = sendControl(args.socket, {
1504
+ v: 1,
1505
+ id: requestId(message),
1506
+ type: 'reply',
1507
+ ok: true,
1508
+ data: recovered,
1509
+ });
1510
+ if (!replySent)
1511
+ return;
1512
+ args.registry.markProviderAgentsDeliveryReady?.(args.workspaceId, args.nodeId, frameProviderName, args.connectionId, [recovered.agent_id]);
1513
+ await deliverPendingToNode(args.db, args.registry, args.workspaceId, args.nodeId, { providerName: frameProviderName, agentIds: [recovered.agent_id] });
1514
+ return;
1515
+ }
1448
1516
  case 'agent.deregister':
1449
1517
  await deregisterAgentViaNode(args.db, args.workspaceId, args.nodeId, message, args.completionDeps);
1450
1518
  return;