agent-relay 1.3.0 → 1.3.2
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/.trajectories/active/traj_3yx9dy148mge.json +42 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
- package/.trajectories/index.json +140 -1
- package/TRAIL_GIT_AUTH_FIX.md +113 -0
- package/deploy/workspace/codex.config.toml +1 -1
- package/deploy/workspace/entrypoint.sh +20 -79
- package/deploy/workspace/gh-relay +156 -0
- package/deploy/workspace/git-credential-relay +5 -1
- package/dist/bridge/multi-project-client.js +13 -10
- package/dist/bridge/spawner.d.ts +2 -0
- package/dist/bridge/spawner.js +19 -1
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +115 -69
- package/dist/cloud/api/admin.js +16 -3
- package/dist/cloud/api/codex-auth-helper.js +28 -8
- package/dist/cloud/api/consensus.d.ts +13 -0
- package/dist/cloud/api/consensus.js +259 -0
- package/dist/cloud/api/daemons.js +205 -1
- package/dist/cloud/api/git.js +37 -7
- package/dist/cloud/api/onboarding.js +4 -1
- package/dist/cloud/api/provider-env.d.ts +5 -0
- package/dist/cloud/api/provider-env.js +27 -0
- package/dist/cloud/api/providers.js +2 -0
- package/dist/cloud/api/test-helpers.js +130 -0
- package/dist/cloud/api/workspaces.js +38 -3
- package/dist/cloud/db/bulk-ingest.d.ts +88 -0
- package/dist/cloud/db/bulk-ingest.js +268 -0
- package/dist/cloud/db/drizzle.d.ts +33 -0
- package/dist/cloud/db/drizzle.js +174 -2
- package/dist/cloud/db/index.d.ts +24 -5
- package/dist/cloud/db/index.js +19 -4
- package/dist/cloud/db/schema.d.ts +397 -3
- package/dist/cloud/db/schema.js +75 -1
- package/dist/cloud/provisioner/index.d.ts +8 -0
- package/dist/cloud/provisioner/index.js +256 -50
- package/dist/cloud/server.js +47 -3
- package/dist/cloud/services/index.d.ts +1 -0
- package/dist/cloud/services/index.js +2 -0
- package/dist/cloud/services/nango.d.ts +3 -4
- package/dist/cloud/services/nango.js +11 -33
- package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
- package/dist/cloud/services/workspace-keepalive.js +234 -0
- package/dist/config/relay-config.d.ts +23 -0
- package/dist/config/relay-config.js +23 -0
- package/dist/daemon/agent-manager.d.ts +20 -1
- package/dist/daemon/agent-manager.js +47 -0
- package/dist/daemon/agent-registry.js +4 -4
- package/dist/daemon/agent-signing.d.ts +158 -0
- package/dist/daemon/agent-signing.js +523 -0
- package/dist/daemon/api.js +18 -1
- package/dist/daemon/cli-auth.d.ts +4 -1
- package/dist/daemon/cli-auth.js +55 -11
- package/dist/daemon/cloud-sync.d.ts +47 -1
- package/dist/daemon/cloud-sync.js +152 -3
- package/dist/daemon/connection.d.ts +28 -0
- package/dist/daemon/connection.js +98 -15
- package/dist/daemon/consensus-integration.d.ts +167 -0
- package/dist/daemon/consensus-integration.js +371 -0
- package/dist/daemon/consensus.d.ts +271 -0
- package/dist/daemon/consensus.js +632 -0
- package/dist/daemon/delivery-tracker.d.ts +34 -0
- package/dist/daemon/delivery-tracker.js +104 -0
- package/dist/daemon/enhanced-features.d.ts +118 -0
- package/dist/daemon/enhanced-features.js +178 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.js +5 -0
- package/dist/daemon/rate-limiter.d.ts +68 -0
- package/dist/daemon/rate-limiter.js +130 -0
- package/dist/daemon/router.d.ts +18 -11
- package/dist/daemon/router.js +55 -111
- package/dist/daemon/server.d.ts +13 -1
- package/dist/daemon/server.js +71 -9
- package/dist/daemon/sync-queue.d.ts +116 -0
- package/dist/daemon/sync-queue.js +361 -0
- package/dist/health-worker-manager.d.ts +62 -0
- package/dist/health-worker-manager.js +144 -0
- package/dist/health-worker.d.ts +9 -0
- package/dist/health-worker.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/memory/context-compaction.d.ts +156 -0
- package/dist/memory/context-compaction.js +453 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/protocol/channels.js +4 -4
- package/dist/protocol/framing.d.ts +72 -10
- package/dist/protocol/framing.js +194 -25
- package/dist/storage/adapter.d.ts +8 -1
- package/dist/storage/adapter.js +11 -0
- package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
- package/dist/storage/batched-sqlite-adapter.js +183 -0
- package/dist/storage/dead-letter-queue.d.ts +196 -0
- package/dist/storage/dead-letter-queue.js +427 -0
- package/dist/storage/dlq-adapter.d.ts +195 -0
- package/dist/storage/dlq-adapter.js +664 -0
- package/dist/trajectory/config.d.ts +32 -14
- package/dist/trajectory/config.js +38 -16
- package/dist/trajectory/integration.js +217 -64
- package/dist/utils/git-remote.d.ts +47 -0
- package/dist/utils/git-remote.js +125 -0
- package/dist/utils/id-generator.d.ts +35 -0
- package/dist/utils/id-generator.js +60 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/precompiled-patterns.d.ts +110 -0
- package/dist/utils/precompiled-patterns.js +322 -0
- package/dist/wrapper/auth-detection.js +1 -1
- package/dist/wrapper/base-wrapper.d.ts +36 -0
- package/dist/wrapper/base-wrapper.js +48 -2
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +84 -31
- package/dist/wrapper/idle-detector.d.ts +102 -0
- package/dist/wrapper/idle-detector.js +279 -0
- package/dist/wrapper/parser.d.ts +4 -0
- package/dist/wrapper/parser.js +19 -1
- package/dist/wrapper/pty-wrapper.d.ts +7 -1
- package/dist/wrapper/pty-wrapper.js +51 -27
- package/dist/wrapper/tmux-wrapper.d.ts +12 -1
- package/dist/wrapper/tmux-wrapper.js +65 -17
- package/package.json +5 -5
- package/scripts/run-migrations.js +43 -0
- package/scripts/verify-schema.js +134 -0
- package/tests/benchmarks/protocol.bench.ts +310 -0
- package/dist/dashboard/out/404.html +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_buildManifest.js +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_ssgManifest.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +0 -2
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +0 -9
- package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-bb19a9b3d9b39ea6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8939b0fc700f7eca.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/page-5af1b6b439858aa6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-f45ecbc3e06134fc.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/history/page-8c8bed33beb2bf1c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16f3b49e55b1e0ed.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-ac39dc0cc3c26fa7.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/page-4a5938c18a11a654.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-982a7000fee44014.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-ac3a6ac433fd6001.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-09f9caae98a18c09.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-547dd0ca55ecd0ba.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +0 -18
- package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-2ee6beb2ae96d210.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +0 -1
- package/dist/dashboard/out/_next/static/css/85d2af9c7ac74d62.css +0 -1
- package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +0 -1
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +0 -45
- package/dist/dashboard/out/alt-logos/logo.svg +0 -38
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +0 -38
- package/dist/dashboard/out/app/onboarding.html +0 -1
- package/dist/dashboard/out/app/onboarding.txt +0 -7
- package/dist/dashboard/out/app.html +0 -1
- package/dist/dashboard/out/app.txt +0 -7
- package/dist/dashboard/out/apple-icon.png +0 -0
- package/dist/dashboard/out/connect-repos.html +0 -1
- package/dist/dashboard/out/connect-repos.txt +0 -7
- package/dist/dashboard/out/history.html +0 -1
- package/dist/dashboard/out/history.txt +0 -7
- package/dist/dashboard/out/index.html +0 -1
- package/dist/dashboard/out/index.txt +0 -7
- package/dist/dashboard/out/login.html +0 -6
- package/dist/dashboard/out/login.txt +0 -7
- package/dist/dashboard/out/metrics.html +0 -1
- package/dist/dashboard/out/metrics.txt +0 -7
- package/dist/dashboard/out/pricing.html +0 -13
- package/dist/dashboard/out/pricing.txt +0 -7
- package/dist/dashboard/out/providers/setup/claude.html +0 -1
- package/dist/dashboard/out/providers/setup/claude.txt +0 -8
- package/dist/dashboard/out/providers/setup/codex.html +0 -1
- package/dist/dashboard/out/providers/setup/codex.txt +0 -8
- package/dist/dashboard/out/providers.html +0 -1
- package/dist/dashboard/out/providers.txt +0 -7
- package/dist/dashboard/out/signup.html +0 -6
- package/dist/dashboard/out/signup.txt +0 -7
- package/dist/dashboard-server/metrics.d.ts +0 -105
- package/dist/dashboard-server/metrics.js +0 -193
- package/dist/dashboard-server/needs-attention.d.ts +0 -24
- package/dist/dashboard-server/needs-attention.js +0 -78
- package/dist/dashboard-server/server.d.ts +0 -15
- package/dist/dashboard-server/server.js +0 -3776
- package/dist/dashboard-server/start.d.ts +0 -6
- package/dist/dashboard-server/start.js +0 -13
- package/dist/dashboard-server/user-bridge.d.ts +0 -103
- package/dist/dashboard-server/user-bridge.js +0 -189
package/dist/daemon/router.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { type Envelope, type SendEnvelope, type AckPayload, type ShadowConfig, t
|
|
|
6
6
|
import type { ChannelJoinPayload, ChannelLeavePayload, ChannelMessagePayload } from '../protocol/channels.js';
|
|
7
7
|
import type { StorageAdapter } from '../storage/adapter.js';
|
|
8
8
|
import type { AgentRegistry } from './agent-registry.js';
|
|
9
|
+
import { type RateLimitConfig } from './rate-limiter.js';
|
|
10
|
+
import { type DeliveryReliabilityOptions } from './delivery-tracker.js';
|
|
9
11
|
export interface RoutableConnection {
|
|
10
12
|
id: string;
|
|
11
13
|
agentName?: string;
|
|
@@ -32,14 +34,6 @@ export interface CrossMachineHandler {
|
|
|
32
34
|
sendCrossMachineMessage(targetDaemonId: string, targetAgent: string, fromAgent: string, content: string, metadata?: Record<string, unknown>): Promise<boolean>;
|
|
33
35
|
isRemoteAgent(agentName: string): RemoteAgentInfo | undefined;
|
|
34
36
|
}
|
|
35
|
-
export interface DeliveryReliabilityOptions {
|
|
36
|
-
/** How long to wait for an ACK before retrying (ms) */
|
|
37
|
-
ackTimeoutMs: number;
|
|
38
|
-
/** Maximum attempts (initial send counts as attempt 1) */
|
|
39
|
-
maxAttempts: number;
|
|
40
|
-
/** How long to keep retrying before dropping (ms) */
|
|
41
|
-
deliveryTtlMs: number;
|
|
42
|
-
}
|
|
43
37
|
/** Internal shadow relationship with resolved defaults */
|
|
44
38
|
interface ShadowRelationship extends ShadowConfig {
|
|
45
39
|
shadowAgent: string;
|
|
@@ -49,11 +43,10 @@ export declare class Router {
|
|
|
49
43
|
private connections;
|
|
50
44
|
private agents;
|
|
51
45
|
private subscriptions;
|
|
52
|
-
private pendingDeliveries;
|
|
53
46
|
private processingAgents;
|
|
54
|
-
private deliveryOptions;
|
|
55
47
|
private registry?;
|
|
56
48
|
private crossMachineHandler?;
|
|
49
|
+
private deliveryTracker;
|
|
57
50
|
/** Shadow relationships: primaryAgent -> list of shadow configs */
|
|
58
51
|
private shadowsByPrimary;
|
|
59
52
|
/** Reverse lookup: shadowAgent -> primaryAgent (for cleanup) */
|
|
@@ -68,12 +61,16 @@ export declare class Router {
|
|
|
68
61
|
private static readonly PROCESSING_TIMEOUT_MS;
|
|
69
62
|
/** Callback when processing state changes (for real-time dashboard updates) */
|
|
70
63
|
private onProcessingStateChange?;
|
|
64
|
+
/** Rate limiter for per-agent throttling */
|
|
65
|
+
private rateLimiter;
|
|
71
66
|
constructor(options?: {
|
|
72
67
|
storage?: StorageAdapter;
|
|
73
68
|
delivery?: Partial<DeliveryReliabilityOptions>;
|
|
74
69
|
registry?: AgentRegistry;
|
|
75
70
|
onProcessingStateChange?: () => void;
|
|
76
71
|
crossMachineHandler?: CrossMachineHandler;
|
|
72
|
+
/** Rate limit configuration. Set to null to disable rate limiting. */
|
|
73
|
+
rateLimit?: Partial<RateLimitConfig> | null;
|
|
77
74
|
});
|
|
78
75
|
/**
|
|
79
76
|
* Set or update the cross-machine handler.
|
|
@@ -177,6 +174,17 @@ export declare class Router {
|
|
|
177
174
|
*/
|
|
178
175
|
get connectionCount(): number;
|
|
179
176
|
get pendingDeliveryCount(): number;
|
|
177
|
+
/**
|
|
178
|
+
* Get rate limiter statistics.
|
|
179
|
+
*/
|
|
180
|
+
getRateLimiterStats(): {
|
|
181
|
+
agentCount: number;
|
|
182
|
+
config: RateLimitConfig;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Reset rate limit for a specific agent (admin operation).
|
|
186
|
+
*/
|
|
187
|
+
resetRateLimit(agentName: string): void;
|
|
180
188
|
/**
|
|
181
189
|
* Get list of agents currently processing (thinking).
|
|
182
190
|
* Returns an object with agent names as keys and processing info as values.
|
|
@@ -209,7 +217,6 @@ export declare class Router {
|
|
|
209
217
|
* Track a delivery and schedule retries until ACKed or TTL/attempts exhausted.
|
|
210
218
|
*/
|
|
211
219
|
private trackDelivery;
|
|
212
|
-
private scheduleRetry;
|
|
213
220
|
/**
|
|
214
221
|
* Broadcast a system message to all connected agents.
|
|
215
222
|
* Used for system notifications like agent death announcements.
|
package/dist/daemon/router.js
CHANGED
|
@@ -2,24 +2,20 @@
|
|
|
2
2
|
* Message router for the agent relay daemon.
|
|
3
3
|
* Handles routing messages between agents, topic subscriptions, and broadcast.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { generateId } from '../utils/id-generator.js';
|
|
6
6
|
import { PROTOCOL_VERSION, } from '../protocol/types.js';
|
|
7
7
|
import { routerLog } from '../utils/logger.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
maxAttempts: 5,
|
|
11
|
-
deliveryTtlMs: 60_000,
|
|
12
|
-
};
|
|
8
|
+
import { RateLimiter, NoOpRateLimiter } from './rate-limiter.js';
|
|
9
|
+
import { DeliveryTracker, } from './delivery-tracker.js';
|
|
13
10
|
export class Router {
|
|
14
11
|
storage;
|
|
15
12
|
connections = new Map(); // connectionId -> Connection
|
|
16
13
|
agents = new Map(); // agentName -> Connection
|
|
17
14
|
subscriptions = new Map(); // topic -> Set<agentName>
|
|
18
|
-
pendingDeliveries = new Map(); // deliverId -> pending
|
|
19
15
|
processingAgents = new Map(); // agentName -> processing state
|
|
20
|
-
deliveryOptions;
|
|
21
16
|
registry;
|
|
22
17
|
crossMachineHandler;
|
|
18
|
+
deliveryTracker;
|
|
23
19
|
/** Shadow relationships: primaryAgent -> list of shadow configs */
|
|
24
20
|
shadowsByPrimary = new Map();
|
|
25
21
|
/** Reverse lookup: shadowAgent -> primaryAgent (for cleanup) */
|
|
@@ -34,12 +30,22 @@ export class Router {
|
|
|
34
30
|
static PROCESSING_TIMEOUT_MS = 30_000;
|
|
35
31
|
/** Callback when processing state changes (for real-time dashboard updates) */
|
|
36
32
|
onProcessingStateChange;
|
|
33
|
+
/** Rate limiter for per-agent throttling */
|
|
34
|
+
rateLimiter;
|
|
37
35
|
constructor(options = {}) {
|
|
38
36
|
this.storage = options.storage;
|
|
39
|
-
this.deliveryOptions = { ...DEFAULT_DELIVERY_OPTIONS, ...options.delivery };
|
|
40
37
|
this.registry = options.registry;
|
|
41
38
|
this.onProcessingStateChange = options.onProcessingStateChange;
|
|
42
39
|
this.crossMachineHandler = options.crossMachineHandler;
|
|
40
|
+
this.deliveryTracker = new DeliveryTracker({
|
|
41
|
+
storage: this.storage,
|
|
42
|
+
delivery: options.delivery,
|
|
43
|
+
getConnection: (id) => this.connections.get(id),
|
|
44
|
+
});
|
|
45
|
+
// Initialize rate limiter (null = disabled)
|
|
46
|
+
this.rateLimiter = options.rateLimit === null
|
|
47
|
+
? new NoOpRateLimiter()
|
|
48
|
+
: new RateLimiter(options.rateLimit);
|
|
43
49
|
}
|
|
44
50
|
/**
|
|
45
51
|
* Set or update the cross-machine handler.
|
|
@@ -239,7 +245,7 @@ export class Router {
|
|
|
239
245
|
const triggerEnvelope = {
|
|
240
246
|
v: PROTOCOL_VERSION,
|
|
241
247
|
type: 'SEND',
|
|
242
|
-
id:
|
|
248
|
+
id: generateId(),
|
|
243
249
|
ts: Date.now(),
|
|
244
250
|
from: primaryAgent,
|
|
245
251
|
to: shadow.shadowAgent,
|
|
@@ -287,6 +293,11 @@ export class Router {
|
|
|
287
293
|
routerLog.warn('Dropping message - sender has no name');
|
|
288
294
|
return;
|
|
289
295
|
}
|
|
296
|
+
// Check rate limit
|
|
297
|
+
if (!this.rateLimiter.tryAcquire(senderName)) {
|
|
298
|
+
routerLog.warn(`Rate limited: ${senderName}`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
290
301
|
// Agent is responding - clear their processing state
|
|
291
302
|
this.clearProcessing(senderName);
|
|
292
303
|
this.registry?.recordSend(senderName);
|
|
@@ -435,22 +446,27 @@ export class Router {
|
|
|
435
446
|
* Broadcast to all agents (optionally filtered by topic subscription).
|
|
436
447
|
*/
|
|
437
448
|
broadcast(from, envelope, topic) {
|
|
449
|
+
// Build recipients list from both agents and users
|
|
438
450
|
const recipients = topic
|
|
439
451
|
? this.subscriptions.get(topic) ?? new Set()
|
|
440
|
-
: new Set(this.agents.keys());
|
|
441
|
-
for (const
|
|
442
|
-
if (
|
|
452
|
+
: new Set([...this.agents.keys(), ...this.users.keys()]);
|
|
453
|
+
for (const recipientName of recipients) {
|
|
454
|
+
if (recipientName === from)
|
|
443
455
|
continue; // Don't send to self
|
|
444
|
-
|
|
456
|
+
// Check both agents and users maps (consistent with sendDirect)
|
|
457
|
+
const target = this.agents.get(recipientName) ?? this.users.get(recipientName);
|
|
445
458
|
if (target) {
|
|
446
|
-
const
|
|
459
|
+
const isUserTarget = target.entityType === 'user';
|
|
460
|
+
const deliver = this.createDeliverEnvelope(from, recipientName, envelope, target);
|
|
447
461
|
const sent = target.send(deliver);
|
|
448
462
|
this.persistDeliverEnvelope(deliver, true); // Mark as broadcast
|
|
449
463
|
if (sent) {
|
|
450
464
|
this.trackDelivery(target, deliver);
|
|
451
|
-
this.registry?.recordReceive(
|
|
452
|
-
//
|
|
453
|
-
|
|
465
|
+
this.registry?.recordReceive(recipientName);
|
|
466
|
+
// Only mark AI agents as processing; humans don't need processing indicators
|
|
467
|
+
if (!isUserTarget) {
|
|
468
|
+
this.setProcessing(recipientName, deliver.id);
|
|
469
|
+
}
|
|
454
470
|
}
|
|
455
471
|
}
|
|
456
472
|
}
|
|
@@ -464,7 +480,7 @@ export class Router {
|
|
|
464
480
|
return {
|
|
465
481
|
v: PROTOCOL_VERSION,
|
|
466
482
|
type: 'DELIVER',
|
|
467
|
-
id:
|
|
483
|
+
id: generateId(),
|
|
468
484
|
ts: Date.now(),
|
|
469
485
|
from,
|
|
470
486
|
to,
|
|
@@ -524,7 +540,19 @@ export class Router {
|
|
|
524
540
|
return this.connections.size;
|
|
525
541
|
}
|
|
526
542
|
get pendingDeliveryCount() {
|
|
527
|
-
return this.
|
|
543
|
+
return this.deliveryTracker.pendingCount;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Get rate limiter statistics.
|
|
547
|
+
*/
|
|
548
|
+
getRateLimiterStats() {
|
|
549
|
+
return this.rateLimiter.getStats();
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Reset rate limit for a specific agent (admin operation).
|
|
553
|
+
*/
|
|
554
|
+
resetRateLimit(agentName) {
|
|
555
|
+
this.rateLimiter.reset(agentName);
|
|
528
556
|
}
|
|
529
557
|
/**
|
|
530
558
|
* Get list of agents currently processing (thinking).
|
|
@@ -580,103 +608,19 @@ export class Router {
|
|
|
580
608
|
*/
|
|
581
609
|
handleAck(connection, envelope) {
|
|
582
610
|
const ackId = envelope.payload.ack_id;
|
|
583
|
-
|
|
584
|
-
if (!pending)
|
|
585
|
-
return;
|
|
586
|
-
// Only accept ACKs from the same connection that received the deliver
|
|
587
|
-
if (pending.connectionId !== connection.id)
|
|
588
|
-
return;
|
|
589
|
-
if (pending.timer) {
|
|
590
|
-
clearTimeout(pending.timer);
|
|
591
|
-
}
|
|
592
|
-
this.pendingDeliveries.delete(ackId);
|
|
593
|
-
const statusUpdate = this.storage?.updateMessageStatus?.(ackId, 'acked');
|
|
594
|
-
if (statusUpdate instanceof Promise) {
|
|
595
|
-
statusUpdate.catch(err => {
|
|
596
|
-
routerLog.error('Failed to record ACK status', { error: String(err) });
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
routerLog.debug(`ACK received for ${ackId}`);
|
|
611
|
+
this.deliveryTracker.handleAck(connection.id, ackId);
|
|
600
612
|
}
|
|
601
613
|
/**
|
|
602
614
|
* Clear pending deliveries for a connection (e.g., on disconnect).
|
|
603
615
|
*/
|
|
604
616
|
clearPendingForConnection(connectionId) {
|
|
605
|
-
|
|
606
|
-
if (pending.connectionId === connectionId) {
|
|
607
|
-
if (pending.timer)
|
|
608
|
-
clearTimeout(pending.timer);
|
|
609
|
-
this.pendingDeliveries.delete(id);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
617
|
+
this.deliveryTracker.clearPendingForConnection(connectionId);
|
|
612
618
|
}
|
|
613
619
|
/**
|
|
614
620
|
* Track a delivery and schedule retries until ACKed or TTL/attempts exhausted.
|
|
615
621
|
*/
|
|
616
622
|
trackDelivery(target, deliver) {
|
|
617
|
-
|
|
618
|
-
envelope: deliver,
|
|
619
|
-
connectionId: target.id,
|
|
620
|
-
attempts: 1,
|
|
621
|
-
firstSentAt: Date.now(),
|
|
622
|
-
};
|
|
623
|
-
pending.timer = this.scheduleRetry(deliver.id);
|
|
624
|
-
this.pendingDeliveries.set(deliver.id, pending);
|
|
625
|
-
}
|
|
626
|
-
scheduleRetry(deliverId) {
|
|
627
|
-
return setTimeout(() => {
|
|
628
|
-
const pending = this.pendingDeliveries.get(deliverId);
|
|
629
|
-
if (!pending)
|
|
630
|
-
return;
|
|
631
|
-
const now = Date.now();
|
|
632
|
-
const elapsed = now - pending.firstSentAt;
|
|
633
|
-
if (elapsed > this.deliveryOptions.deliveryTtlMs) {
|
|
634
|
-
routerLog.warn(`Dropping ${deliverId} after TTL`, { ttlMs: this.deliveryOptions.deliveryTtlMs });
|
|
635
|
-
this.pendingDeliveries.delete(deliverId);
|
|
636
|
-
// Mark message as failed in storage
|
|
637
|
-
const statusUpdate = this.storage?.updateMessageStatus?.(deliverId, 'failed');
|
|
638
|
-
if (statusUpdate instanceof Promise) {
|
|
639
|
-
statusUpdate.catch(err => {
|
|
640
|
-
routerLog.error(`Failed to update status for ${deliverId}`, { error: String(err) });
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
if (pending.attempts >= this.deliveryOptions.maxAttempts) {
|
|
646
|
-
routerLog.warn(`Dropping ${deliverId} after max attempts`, { maxAttempts: this.deliveryOptions.maxAttempts });
|
|
647
|
-
this.pendingDeliveries.delete(deliverId);
|
|
648
|
-
// Mark message as failed in storage
|
|
649
|
-
const statusUpdate = this.storage?.updateMessageStatus?.(deliverId, 'failed');
|
|
650
|
-
if (statusUpdate instanceof Promise) {
|
|
651
|
-
statusUpdate.catch(err => {
|
|
652
|
-
routerLog.error(`Failed to update status for ${deliverId}`, { error: String(err) });
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
const target = this.connections.get(pending.connectionId);
|
|
658
|
-
if (!target) {
|
|
659
|
-
routerLog.warn(`Dropping ${deliverId} - connection unavailable`);
|
|
660
|
-
this.pendingDeliveries.delete(deliverId);
|
|
661
|
-
// Mark message as failed in storage
|
|
662
|
-
const statusUpdate = this.storage?.updateMessageStatus?.(deliverId, 'failed');
|
|
663
|
-
if (statusUpdate instanceof Promise) {
|
|
664
|
-
statusUpdate.catch(err => {
|
|
665
|
-
routerLog.error(`Failed to update status for ${deliverId}`, { error: String(err) });
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
pending.attempts++;
|
|
671
|
-
const sent = target.send(pending.envelope);
|
|
672
|
-
if (!sent) {
|
|
673
|
-
routerLog.warn(`Retry failed for ${deliverId}`, { attempt: pending.attempts });
|
|
674
|
-
}
|
|
675
|
-
else {
|
|
676
|
-
routerLog.debug(`Retried ${deliverId}`, { attempt: pending.attempts });
|
|
677
|
-
}
|
|
678
|
-
pending.timer = this.scheduleRetry(deliverId);
|
|
679
|
-
}, this.deliveryOptions.ackTimeoutMs);
|
|
623
|
+
this.deliveryTracker.track(target, deliver);
|
|
680
624
|
}
|
|
681
625
|
/**
|
|
682
626
|
* Broadcast a system message to all connected agents.
|
|
@@ -686,7 +630,7 @@ export class Router {
|
|
|
686
630
|
const envelope = {
|
|
687
631
|
v: PROTOCOL_VERSION,
|
|
688
632
|
type: 'SEND',
|
|
689
|
-
id:
|
|
633
|
+
id: generateId(),
|
|
690
634
|
ts: Date.now(),
|
|
691
635
|
from: '_system',
|
|
692
636
|
to: '*',
|
|
@@ -776,7 +720,7 @@ export class Router {
|
|
|
776
720
|
const joinNotification = {
|
|
777
721
|
v: PROTOCOL_VERSION,
|
|
778
722
|
type: 'CHANNEL_JOIN',
|
|
779
|
-
id:
|
|
723
|
+
id: generateId(),
|
|
780
724
|
ts: Date.now(),
|
|
781
725
|
from: memberName,
|
|
782
726
|
payload: envelope.payload,
|
|
@@ -828,7 +772,7 @@ export class Router {
|
|
|
828
772
|
const leaveNotification = {
|
|
829
773
|
v: PROTOCOL_VERSION,
|
|
830
774
|
type: 'CHANNEL_LEAVE',
|
|
831
|
-
id:
|
|
775
|
+
id: generateId(),
|
|
832
776
|
ts: Date.now(),
|
|
833
777
|
from: memberName,
|
|
834
778
|
payload: envelope.payload,
|
|
@@ -871,7 +815,7 @@ export class Router {
|
|
|
871
815
|
const deliverEnvelope = {
|
|
872
816
|
v: PROTOCOL_VERSION,
|
|
873
817
|
type: 'CHANNEL_MESSAGE',
|
|
874
|
-
id:
|
|
818
|
+
id: generateId(),
|
|
875
819
|
ts: Date.now(),
|
|
876
820
|
from: senderName,
|
|
877
821
|
payload: envelope.payload,
|
package/dist/daemon/server.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { type ConnectionConfig } from './connection.js';
|
|
6
6
|
import { type StorageAdapter, type StorageConfig } from '../storage/adapter.js';
|
|
7
7
|
import { type RemoteAgent } from './cloud-sync.js';
|
|
8
|
+
import { ConsensusIntegration, type ConsensusIntegrationConfig } from './consensus-integration.js';
|
|
8
9
|
export interface DaemonConfig extends ConnectionConfig {
|
|
9
10
|
socketPath: string;
|
|
10
11
|
pidFilePath: string;
|
|
@@ -16,8 +17,10 @@ export interface DaemonConfig extends ConnectionConfig {
|
|
|
16
17
|
teamDir?: string;
|
|
17
18
|
/** Enable cloud sync for cross-machine agent communication */
|
|
18
19
|
cloudSync?: boolean;
|
|
19
|
-
/** Cloud API URL (defaults to https://
|
|
20
|
+
/** Cloud API URL (defaults to https://agent-relay.com) */
|
|
20
21
|
cloudUrl?: string;
|
|
22
|
+
/** Consensus mechanism for multi-agent decisions (enabled by default, set to false to disable) */
|
|
23
|
+
consensus?: boolean | Partial<ConsensusIntegrationConfig>;
|
|
21
24
|
}
|
|
22
25
|
export declare const DEFAULT_SOCKET_PATH = "/tmp/agent-relay.sock";
|
|
23
26
|
export declare const DEFAULT_DAEMON_CONFIG: DaemonConfig;
|
|
@@ -33,6 +36,7 @@ export declare class Daemon {
|
|
|
33
36
|
private processingStateInterval?;
|
|
34
37
|
private cloudSync?;
|
|
35
38
|
private remoteAgents;
|
|
39
|
+
private consensus?;
|
|
36
40
|
/** Callback for log output from agents (used by dashboard for streaming) */
|
|
37
41
|
onLogOutput?: (agentName: string, data: string, timestamp: number) => void;
|
|
38
42
|
/** Interval for writing processing state file (500ms for responsive UI) */
|
|
@@ -112,5 +116,13 @@ export declare class Daemon {
|
|
|
112
116
|
* Check if daemon is running.
|
|
113
117
|
*/
|
|
114
118
|
get isRunning(): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Check if consensus is enabled.
|
|
121
|
+
*/
|
|
122
|
+
get consensusEnabled(): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Get the consensus integration (for API access).
|
|
125
|
+
*/
|
|
126
|
+
getConsensus(): ConsensusIntegration | undefined;
|
|
115
127
|
}
|
|
116
128
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/daemon/server.js
CHANGED
|
@@ -14,7 +14,8 @@ import { getProjectPaths } from '../utils/project-namespace.js';
|
|
|
14
14
|
import { AgentRegistry } from './agent-registry.js';
|
|
15
15
|
import { daemonLog as log } from '../utils/logger.js';
|
|
16
16
|
import { getCloudSync } from './cloud-sync.js';
|
|
17
|
-
import {
|
|
17
|
+
import { generateId } from '../utils/id-generator.js';
|
|
18
|
+
import { createConsensusIntegration, } from './consensus-integration.js';
|
|
18
19
|
export const DEFAULT_SOCKET_PATH = '/tmp/agent-relay.sock';
|
|
19
20
|
export const DEFAULT_DAEMON_CONFIG = {
|
|
20
21
|
...DEFAULT_CONFIG,
|
|
@@ -33,6 +34,7 @@ export class Daemon {
|
|
|
33
34
|
processingStateInterval;
|
|
34
35
|
cloudSync;
|
|
35
36
|
remoteAgents = [];
|
|
37
|
+
consensus;
|
|
36
38
|
/** Callback for log output from agents (used by dashboard for streaming) */
|
|
37
39
|
onLogOutput;
|
|
38
40
|
/** Interval for writing processing state file (500ms for responsive UI) */
|
|
@@ -115,6 +117,14 @@ export class Daemon {
|
|
|
115
117
|
isRemoteAgent: this.isRemoteAgent.bind(this),
|
|
116
118
|
},
|
|
117
119
|
});
|
|
120
|
+
// Initialize consensus (enabled by default, can be disabled with consensus: false)
|
|
121
|
+
if (this.config.consensus !== false) {
|
|
122
|
+
const consensusConfig = typeof this.config.consensus === 'object'
|
|
123
|
+
? this.config.consensus
|
|
124
|
+
: {};
|
|
125
|
+
this.consensus = createConsensusIntegration(this.router, consensusConfig);
|
|
126
|
+
log.info('Consensus mechanism enabled');
|
|
127
|
+
}
|
|
118
128
|
this.storageInitialized = true;
|
|
119
129
|
}
|
|
120
130
|
/**
|
|
@@ -160,20 +170,41 @@ export class Daemon {
|
|
|
160
170
|
* Initialize cloud sync service for cross-machine agent communication.
|
|
161
171
|
*/
|
|
162
172
|
async initCloudSync() {
|
|
163
|
-
// Check for cloud config file
|
|
173
|
+
// Check for cloud config file OR environment variables
|
|
164
174
|
const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
|
|
165
175
|
path.join(os.homedir(), '.local', 'share', 'agent-relay');
|
|
166
176
|
const configPath = path.join(dataDir, 'cloud-config.json');
|
|
167
|
-
|
|
177
|
+
const hasConfigFile = fs.existsSync(configPath);
|
|
178
|
+
const hasEnvApiKey = !!process.env.AGENT_RELAY_API_KEY;
|
|
179
|
+
// Allow cloud sync if config file exists OR API key is set via env var
|
|
180
|
+
// This enables cloud-hosted workspaces (Fly.io) to sync messages without a config file
|
|
181
|
+
if (!hasConfigFile && !hasEnvApiKey) {
|
|
168
182
|
log.info('Cloud sync disabled (not linked to cloud)');
|
|
169
183
|
return;
|
|
170
184
|
}
|
|
171
185
|
try {
|
|
172
|
-
|
|
186
|
+
let apiKey;
|
|
187
|
+
let cloudUrl;
|
|
188
|
+
if (hasConfigFile) {
|
|
189
|
+
// Use config file (local daemons linked via CLI)
|
|
190
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
191
|
+
apiKey = config.apiKey;
|
|
192
|
+
cloudUrl = config.cloudUrl;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Use env vars (cloud-hosted workspaces like Fly.io)
|
|
196
|
+
apiKey = process.env.AGENT_RELAY_API_KEY;
|
|
197
|
+
// CLOUD_API_URL is set by Fly.io provisioner, AGENT_RELAY_CLOUD_URL is the standard
|
|
198
|
+
cloudUrl = process.env.AGENT_RELAY_CLOUD_URL || process.env.CLOUD_API_URL;
|
|
199
|
+
log.info('Using environment variables for cloud sync', { hasApiKey: !!apiKey, hasCloudUrl: !!cloudUrl });
|
|
200
|
+
}
|
|
201
|
+
// Get project root for workspace detection via git remote
|
|
202
|
+
const projectPaths = getProjectPaths();
|
|
173
203
|
this.cloudSync = getCloudSync({
|
|
174
|
-
apiKey
|
|
175
|
-
cloudUrl:
|
|
204
|
+
apiKey,
|
|
205
|
+
cloudUrl: cloudUrl || this.config.cloudUrl,
|
|
176
206
|
enabled: this.config.cloudSync !== false,
|
|
207
|
+
projectDirectory: projectPaths.projectRoot,
|
|
177
208
|
});
|
|
178
209
|
// Listen for remote agent updates
|
|
179
210
|
this.cloudSync.on('remote-agents-updated', (agents) => {
|
|
@@ -191,6 +222,10 @@ export class Daemon {
|
|
|
191
222
|
// Handle commands like credential updates, config changes, etc.
|
|
192
223
|
});
|
|
193
224
|
await this.cloudSync.start();
|
|
225
|
+
// Set storage adapter for message sync to cloud
|
|
226
|
+
if (this.storage) {
|
|
227
|
+
this.cloudSync.setStorage(this.storage);
|
|
228
|
+
}
|
|
194
229
|
log.info('Cloud sync enabled');
|
|
195
230
|
}
|
|
196
231
|
catch (err) {
|
|
@@ -233,7 +268,7 @@ export class Daemon {
|
|
|
233
268
|
const envelope = {
|
|
234
269
|
v: 1,
|
|
235
270
|
type: 'SEND',
|
|
236
|
-
id:
|
|
271
|
+
id: generateId(),
|
|
237
272
|
ts: Date.now(),
|
|
238
273
|
from: `${msg.from.daemonName}:${msg.from.agent}`,
|
|
239
274
|
to: msg.to,
|
|
@@ -461,9 +496,24 @@ export class Daemon {
|
|
|
461
496
|
*/
|
|
462
497
|
handleMessage(connection, envelope) {
|
|
463
498
|
switch (envelope.type) {
|
|
464
|
-
case 'SEND':
|
|
465
|
-
|
|
499
|
+
case 'SEND': {
|
|
500
|
+
const sendEnvelope = envelope;
|
|
501
|
+
// Check for consensus commands (messages to _consensus)
|
|
502
|
+
if (this.consensus?.enabled && sendEnvelope.to === '_consensus') {
|
|
503
|
+
const from = connection.agentName ?? 'unknown';
|
|
504
|
+
const result = this.consensus.processIncomingMessage(from, sendEnvelope.payload.body);
|
|
505
|
+
if (result.isConsensusCommand) {
|
|
506
|
+
log.info(`Consensus ${result.type} from ${from}`, {
|
|
507
|
+
success: result.result?.success,
|
|
508
|
+
proposalId: result.result?.proposal?.id,
|
|
509
|
+
});
|
|
510
|
+
// Don't route consensus commands to the router
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
this.router.route(connection, sendEnvelope);
|
|
466
515
|
break;
|
|
516
|
+
}
|
|
467
517
|
case 'SUBSCRIBE':
|
|
468
518
|
if (connection.agentName && envelope.topic) {
|
|
469
519
|
this.router.subscribe(connection.agentName, envelope.topic);
|
|
@@ -532,6 +582,18 @@ export class Daemon {
|
|
|
532
582
|
get isRunning() {
|
|
533
583
|
return this.running;
|
|
534
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Check if consensus is enabled.
|
|
587
|
+
*/
|
|
588
|
+
get consensusEnabled() {
|
|
589
|
+
return this.consensus?.enabled ?? false;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Get the consensus integration (for API access).
|
|
593
|
+
*/
|
|
594
|
+
getConsensus() {
|
|
595
|
+
return this.consensus;
|
|
596
|
+
}
|
|
535
597
|
}
|
|
536
598
|
// Run as standalone if executed directly
|
|
537
599
|
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimized Cloud Sync Queue
|
|
3
|
+
*
|
|
4
|
+
* Handles batched, compressed, resilient message syncing to cloud.
|
|
5
|
+
* Features:
|
|
6
|
+
* - Adaptive batching (size/time/bytes triggers)
|
|
7
|
+
* - Gzip compression for payloads over threshold
|
|
8
|
+
* - Disk spillover for offline resilience
|
|
9
|
+
* - Retry with exponential backoff
|
|
10
|
+
* - Startup reconciliation
|
|
11
|
+
*/
|
|
12
|
+
import type { StoredMessage } from '../storage/adapter.js';
|
|
13
|
+
export interface SyncQueueConfig {
|
|
14
|
+
/** Cloud API URL */
|
|
15
|
+
cloudUrl: string;
|
|
16
|
+
/** API key for authentication */
|
|
17
|
+
apiKey: string;
|
|
18
|
+
/** Maximum messages per batch (default: 100) */
|
|
19
|
+
batchSize: number;
|
|
20
|
+
/** Maximum time to wait before flush in ms (default: 200) */
|
|
21
|
+
batchDelayMs: number;
|
|
22
|
+
/** Maximum bytes in memory before flush (default: 512KB) */
|
|
23
|
+
maxBatchBytes: number;
|
|
24
|
+
/** Compress payloads larger than this (default: 1KB) */
|
|
25
|
+
compressionThreshold: number;
|
|
26
|
+
/** Directory for spill files (default: /tmp/agent-relay-sync) */
|
|
27
|
+
spillDir: string;
|
|
28
|
+
/** Maximum spill files to keep (default: 100) */
|
|
29
|
+
maxSpillFiles: number;
|
|
30
|
+
/** Maximum retry attempts (default: 3) */
|
|
31
|
+
maxRetries: number;
|
|
32
|
+
/** Base retry delay in ms (default: 1000) */
|
|
33
|
+
retryDelayMs: number;
|
|
34
|
+
/** Log sync operations (default: false) */
|
|
35
|
+
verbose: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare const DEFAULT_SYNC_QUEUE_CONFIG: SyncQueueConfig;
|
|
38
|
+
export interface SyncResult {
|
|
39
|
+
synced: number;
|
|
40
|
+
duplicates: number;
|
|
41
|
+
failed: number;
|
|
42
|
+
compressed: boolean;
|
|
43
|
+
bytesTransferred: number;
|
|
44
|
+
}
|
|
45
|
+
export interface SyncQueueStats {
|
|
46
|
+
queuedMessages: number;
|
|
47
|
+
queuedBytes: number;
|
|
48
|
+
totalSynced: number;
|
|
49
|
+
totalFailed: number;
|
|
50
|
+
totalCompressed: number;
|
|
51
|
+
totalBytesTransferred: number;
|
|
52
|
+
spilledFiles: number;
|
|
53
|
+
lastSyncAt?: number;
|
|
54
|
+
lastError?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare class SyncQueue {
|
|
57
|
+
private config;
|
|
58
|
+
private queue;
|
|
59
|
+
private queueBytes;
|
|
60
|
+
private flushTimer?;
|
|
61
|
+
private flushing;
|
|
62
|
+
private flushPromise?;
|
|
63
|
+
private stats;
|
|
64
|
+
constructor(config: Partial<SyncQueueConfig>);
|
|
65
|
+
/**
|
|
66
|
+
* Queue a message for sync to cloud.
|
|
67
|
+
* May trigger an immediate flush if thresholds are exceeded.
|
|
68
|
+
*/
|
|
69
|
+
enqueue(message: StoredMessage): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Enqueue multiple messages at once.
|
|
72
|
+
*/
|
|
73
|
+
enqueueBatch(messages: StoredMessage[]): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Flush all queued messages to cloud.
|
|
76
|
+
*/
|
|
77
|
+
flush(): Promise<SyncResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Sync a batch of messages to cloud with retry and spillover.
|
|
80
|
+
*/
|
|
81
|
+
private syncBatch;
|
|
82
|
+
/**
|
|
83
|
+
* Send messages to cloud API with optional compression.
|
|
84
|
+
*/
|
|
85
|
+
private sendToCloud;
|
|
86
|
+
/**
|
|
87
|
+
* Spill failed batch to disk for later recovery.
|
|
88
|
+
*/
|
|
89
|
+
private spillToDisk;
|
|
90
|
+
/**
|
|
91
|
+
* Recover and sync messages from spill files.
|
|
92
|
+
* Call this on startup to resume failed syncs.
|
|
93
|
+
*/
|
|
94
|
+
recoverSpilledMessages(): Promise<{
|
|
95
|
+
recovered: number;
|
|
96
|
+
failed: number;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Cleanup old spill files beyond the limit.
|
|
100
|
+
*/
|
|
101
|
+
private cleanupSpillFiles;
|
|
102
|
+
/**
|
|
103
|
+
* Get sync queue statistics.
|
|
104
|
+
*/
|
|
105
|
+
getStats(): SyncQueueStats;
|
|
106
|
+
/**
|
|
107
|
+
* Reset statistics (for testing or periodic reporting).
|
|
108
|
+
*/
|
|
109
|
+
resetStats(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Gracefully close the queue, flushing any pending messages.
|
|
112
|
+
*/
|
|
113
|
+
close(): Promise<void>;
|
|
114
|
+
private sleep;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=sync-queue.d.ts.map
|