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/api.js
CHANGED
|
@@ -275,6 +275,22 @@ export class DaemonApi extends EventEmitter {
|
|
|
275
275
|
}
|
|
276
276
|
return { status: 200, body: { success: true } };
|
|
277
277
|
});
|
|
278
|
+
// Interrupt agent by ID (send Ctrl+C to break out of stuck loops)
|
|
279
|
+
this.routes.set('POST /agents/:id/interrupt', async (req) => {
|
|
280
|
+
const interrupted = this.agentManager.interrupt(req.params.id);
|
|
281
|
+
if (!interrupted) {
|
|
282
|
+
return { status: 404, body: { error: 'Agent not found' } };
|
|
283
|
+
}
|
|
284
|
+
return { status: 200, body: { success: true } };
|
|
285
|
+
});
|
|
286
|
+
// Interrupt agent by name (for dashboard where only name is available)
|
|
287
|
+
this.routes.set('POST /agents/by-name/:name/interrupt', async (req) => {
|
|
288
|
+
const interrupted = this.agentManager.interruptByName(req.params.name);
|
|
289
|
+
if (!interrupted) {
|
|
290
|
+
return { status: 404, body: { error: 'Agent not found' } };
|
|
291
|
+
}
|
|
292
|
+
return { status: 200, body: { success: true } };
|
|
293
|
+
});
|
|
278
294
|
// === All Agents ===
|
|
279
295
|
// List all agents
|
|
280
296
|
this.routes.set('GET /agents', async () => {
|
|
@@ -449,8 +465,9 @@ export class DaemonApi extends EventEmitter {
|
|
|
449
465
|
// Check if provider is authenticated (credentials exist)
|
|
450
466
|
this.routes.set('GET /auth/cli/:provider/check', async (req) => {
|
|
451
467
|
const { provider } = req.params;
|
|
468
|
+
const userId = req.query.userId;
|
|
452
469
|
const { checkProviderAuth } = await import('./cli-auth.js');
|
|
453
|
-
const authenticated = await checkProviderAuth(provider);
|
|
470
|
+
const authenticated = await checkProviderAuth(provider, userId);
|
|
454
471
|
return { status: 200, body: { authenticated } };
|
|
455
472
|
});
|
|
456
473
|
// === Repository Management ===
|
|
@@ -14,6 +14,7 @@ export type { CLIAuthConfig, PromptHandler };
|
|
|
14
14
|
interface AuthSession {
|
|
15
15
|
id: string;
|
|
16
16
|
provider: string;
|
|
17
|
+
userId?: string;
|
|
17
18
|
status: 'starting' | 'waiting_auth' | 'success' | 'error';
|
|
18
19
|
authUrl?: string;
|
|
19
20
|
token?: string;
|
|
@@ -32,6 +33,8 @@ interface AuthSession {
|
|
|
32
33
|
export interface StartCLIAuthOptions {
|
|
33
34
|
/** Use device flow instead of standard OAuth (if provider supports it) */
|
|
34
35
|
useDeviceFlow?: boolean;
|
|
36
|
+
/** User ID for per-user credential storage (multi-user workspaces) */
|
|
37
|
+
userId?: string;
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
37
40
|
* Start CLI auth flow
|
|
@@ -75,5 +78,5 @@ export declare function cancelAuthSession(sessionId: string): boolean;
|
|
|
75
78
|
* Check if a provider is authenticated (credentials exist)
|
|
76
79
|
* Used by the auth check endpoint for SSH tunnel flow
|
|
77
80
|
*/
|
|
78
|
-
export declare function checkProviderAuth(provider: string): Promise<boolean>;
|
|
81
|
+
export declare function checkProviderAuth(provider: string, userId?: string): Promise<boolean>;
|
|
79
82
|
//# sourceMappingURL=cli-auth.d.ts.map
|
package/dist/daemon/cli-auth.js
CHANGED
|
@@ -10,6 +10,7 @@ import * as fs from 'fs/promises';
|
|
|
10
10
|
import * as os from 'os';
|
|
11
11
|
import { createLogger } from '../resiliency/logger.js';
|
|
12
12
|
import { CLI_AUTH_CONFIG, stripAnsiCodes, matchesSuccessPattern, findMatchingPrompt, findMatchingError, getSupportedProviders, } from '../shared/cli-auth-config.js';
|
|
13
|
+
import { getUserDirectoryService } from './user-directory.js';
|
|
13
14
|
const logger = createLogger('cli-auth');
|
|
14
15
|
// Re-export for consumers
|
|
15
16
|
export { CLI_AUTH_CONFIG, getSupportedProviders };
|
|
@@ -47,6 +48,7 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
47
48
|
const session = {
|
|
48
49
|
id: sessionId,
|
|
49
50
|
provider,
|
|
51
|
+
userId: options.userId,
|
|
50
52
|
status: 'starting',
|
|
51
53
|
output: '',
|
|
52
54
|
promptsHandled: [],
|
|
@@ -61,7 +63,7 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
61
63
|
});
|
|
62
64
|
// Check if already authenticated (credentials exist)
|
|
63
65
|
try {
|
|
64
|
-
const existingCreds = await extractCredentials(provider, config);
|
|
66
|
+
const existingCreds = await extractCredentials(provider, config, options.userId);
|
|
65
67
|
if (existingCreds?.token) {
|
|
66
68
|
logger.info('Already authenticated - existing credentials found', { provider, sessionId });
|
|
67
69
|
session.status = 'success';
|
|
@@ -97,6 +99,27 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
97
99
|
resolveAuthUrl();
|
|
98
100
|
}, AUTH_URL_WAIT_TIMEOUT);
|
|
99
101
|
try {
|
|
102
|
+
// Get per-user environment if userId provided (for multi-user workspaces)
|
|
103
|
+
// This sets HOME to /data/users/{userId} so CLI stores credentials per-user
|
|
104
|
+
let userEnv = {};
|
|
105
|
+
if (options.userId) {
|
|
106
|
+
try {
|
|
107
|
+
const userDirService = getUserDirectoryService();
|
|
108
|
+
userEnv = userDirService.getUserEnvironment(options.userId);
|
|
109
|
+
logger.info('Using per-user environment for CLI auth', {
|
|
110
|
+
provider,
|
|
111
|
+
userId: options.userId,
|
|
112
|
+
home: userEnv.HOME,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
logger.warn('Failed to get user environment, using default', {
|
|
117
|
+
provider,
|
|
118
|
+
userId: options.userId,
|
|
119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
100
123
|
const proc = pty.spawn(config.command, args, {
|
|
101
124
|
name: 'xterm-256color',
|
|
102
125
|
cols: 120,
|
|
@@ -104,6 +127,7 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
104
127
|
cwd: process.cwd(),
|
|
105
128
|
env: {
|
|
106
129
|
...process.env,
|
|
130
|
+
...userEnv, // Override HOME for per-user credential storage
|
|
107
131
|
NO_COLOR: '1',
|
|
108
132
|
TERM: 'xterm-256color',
|
|
109
133
|
// Don't set BROWSER - let CLI fail to open browser and fall back to manual paste mode
|
|
@@ -198,7 +222,7 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
198
222
|
return;
|
|
199
223
|
}
|
|
200
224
|
try {
|
|
201
|
-
const creds = await extractCredentials(provider, config);
|
|
225
|
+
const creds = await extractCredentials(provider, config, session.userId);
|
|
202
226
|
if (creds) {
|
|
203
227
|
session.token = creds.token;
|
|
204
228
|
session.refreshToken = creds.refreshToken;
|
|
@@ -233,7 +257,7 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
233
257
|
// CLI might exit cleanly (code 0) even after an OAuth error
|
|
234
258
|
if ((session.authUrl || exitCode === 0) && session.status !== 'error') {
|
|
235
259
|
try {
|
|
236
|
-
const creds = await extractCredentials(provider, config);
|
|
260
|
+
const creds = await extractCredentials(provider, config, session.userId);
|
|
237
261
|
if (creds) {
|
|
238
262
|
session.token = creds.token;
|
|
239
263
|
session.refreshToken = creds.refreshToken;
|
|
@@ -321,7 +345,7 @@ export async function submitAuthCode(sessionId, code, state) {
|
|
|
321
345
|
const config = CLI_AUTH_CONFIG[session.provider];
|
|
322
346
|
if (config && session.status !== 'error') {
|
|
323
347
|
try {
|
|
324
|
-
const creds = await extractCredentials(session.provider, config);
|
|
348
|
+
const creds = await extractCredentials(session.provider, config, session.userId);
|
|
325
349
|
// Re-check status after async operation (race condition protection)
|
|
326
350
|
// Use type assertion because TypeScript narrowing doesn't account for async race conditions
|
|
327
351
|
if (creds && session.status !== 'error') {
|
|
@@ -465,7 +489,7 @@ async function pollForCredentials(session, config) {
|
|
|
465
489
|
return;
|
|
466
490
|
}
|
|
467
491
|
try {
|
|
468
|
-
const creds = await extractCredentials(session.provider, config);
|
|
492
|
+
const creds = await extractCredentials(session.provider, config, session.userId);
|
|
469
493
|
if (creds) {
|
|
470
494
|
// Double-check we're not in error state (race condition protection)
|
|
471
495
|
// Use type assertion because TypeScript narrowing doesn't account for async race conditions
|
|
@@ -522,7 +546,7 @@ export async function completeAuthSession(sessionId) {
|
|
|
522
546
|
return { success: false, error: session.error || 'Authentication failed' };
|
|
523
547
|
}
|
|
524
548
|
try {
|
|
525
|
-
const creds = await extractCredentials(session.provider, config);
|
|
549
|
+
const creds = await extractCredentials(session.provider, config, session.userId);
|
|
526
550
|
if (creds) {
|
|
527
551
|
// Double-check we're not in error state (race condition protection)
|
|
528
552
|
// Use type assertion because TypeScript narrowing doesn't account for async race conditions
|
|
@@ -568,14 +592,34 @@ export function cancelAuthSession(sessionId) {
|
|
|
568
592
|
sessions.delete(sessionId);
|
|
569
593
|
return true;
|
|
570
594
|
}
|
|
595
|
+
function resolveCredentialPath(provider, config, userId) {
|
|
596
|
+
if (!config.credentialPath)
|
|
597
|
+
return null;
|
|
598
|
+
if (!userId) {
|
|
599
|
+
return config.credentialPath.replace('~', os.homedir());
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const userDirService = getUserDirectoryService();
|
|
603
|
+
const userHome = userDirService.getUserHome(userId);
|
|
604
|
+
return config.credentialPath.replace('~', userHome);
|
|
605
|
+
}
|
|
606
|
+
catch (err) {
|
|
607
|
+
logger.warn('Failed to resolve per-user credential path, using default', {
|
|
608
|
+
provider,
|
|
609
|
+
userId,
|
|
610
|
+
error: err instanceof Error ? err.message : String(err),
|
|
611
|
+
});
|
|
612
|
+
return config.credentialPath.replace('~', os.homedir());
|
|
613
|
+
}
|
|
614
|
+
}
|
|
571
615
|
/**
|
|
572
616
|
* Extract credentials from CLI credential file
|
|
573
617
|
*/
|
|
574
|
-
async function extractCredentials(provider, config) {
|
|
575
|
-
|
|
618
|
+
async function extractCredentials(provider, config, userId) {
|
|
619
|
+
const credPath = resolveCredentialPath(provider, config, userId);
|
|
620
|
+
if (!credPath)
|
|
576
621
|
return null;
|
|
577
622
|
try {
|
|
578
|
-
const credPath = config.credentialPath.replace('~', os.homedir());
|
|
579
623
|
const content = await fs.readFile(credPath, 'utf8');
|
|
580
624
|
const creds = JSON.parse(content);
|
|
581
625
|
// Extract token based on provider
|
|
@@ -640,13 +684,13 @@ async function extractCredentials(provider, config) {
|
|
|
640
684
|
* Check if a provider is authenticated (credentials exist)
|
|
641
685
|
* Used by the auth check endpoint for SSH tunnel flow
|
|
642
686
|
*/
|
|
643
|
-
export async function checkProviderAuth(provider) {
|
|
687
|
+
export async function checkProviderAuth(provider, userId) {
|
|
644
688
|
const config = CLI_AUTH_CONFIG[provider];
|
|
645
689
|
if (!config) {
|
|
646
690
|
return false;
|
|
647
691
|
}
|
|
648
692
|
try {
|
|
649
|
-
const creds = await extractCredentials(provider, config);
|
|
693
|
+
const creds = await extractCredentials(provider, config, userId);
|
|
650
694
|
return !!creds?.token;
|
|
651
695
|
}
|
|
652
696
|
catch {
|
|
@@ -8,11 +8,23 @@
|
|
|
8
8
|
* - Credential sync from cloud
|
|
9
9
|
*/
|
|
10
10
|
import { EventEmitter } from 'events';
|
|
11
|
+
import type { StorageAdapter, StoredMessage } from '../storage/adapter.js';
|
|
12
|
+
import { type SyncQueueConfig, type SyncQueueStats } from './sync-queue.js';
|
|
11
13
|
export interface CloudSyncConfig {
|
|
12
14
|
apiKey?: string;
|
|
13
15
|
cloudUrl: string;
|
|
14
16
|
heartbeatInterval: number;
|
|
15
17
|
enabled: boolean;
|
|
18
|
+
/** Enable message sync to cloud (default: true if connected) */
|
|
19
|
+
messageSyncEnabled?: boolean;
|
|
20
|
+
/** Batch size for message sync (default: 100) */
|
|
21
|
+
messageSyncBatchSize?: number;
|
|
22
|
+
/** Use optimized sync queue with compression and spillover (default: true) */
|
|
23
|
+
useOptimizedSync?: boolean;
|
|
24
|
+
/** Sync queue configuration */
|
|
25
|
+
syncQueue?: Partial<SyncQueueConfig>;
|
|
26
|
+
/** Project directory for git remote detection (defaults to cwd) */
|
|
27
|
+
projectDirectory?: string;
|
|
16
28
|
}
|
|
17
29
|
export interface RemoteAgent {
|
|
18
30
|
name: string;
|
|
@@ -39,6 +51,12 @@ export declare class CloudSyncService extends EventEmitter {
|
|
|
39
51
|
private localAgents;
|
|
40
52
|
private remoteAgents;
|
|
41
53
|
private connected;
|
|
54
|
+
private storage;
|
|
55
|
+
private lastMessageSyncTs;
|
|
56
|
+
private messageSyncInProgress;
|
|
57
|
+
private projectDirectory;
|
|
58
|
+
private repoFullName;
|
|
59
|
+
private syncQueue;
|
|
42
60
|
constructor(config?: Partial<CloudSyncConfig>);
|
|
43
61
|
/**
|
|
44
62
|
* Get or create a persistent machine ID
|
|
@@ -51,7 +69,7 @@ export declare class CloudSyncService extends EventEmitter {
|
|
|
51
69
|
/**
|
|
52
70
|
* Stop the cloud sync service
|
|
53
71
|
*/
|
|
54
|
-
stop(): void
|
|
72
|
+
stop(): Promise<void>;
|
|
55
73
|
/**
|
|
56
74
|
* Update local agent list (called by daemon when agents change)
|
|
57
75
|
*/
|
|
@@ -96,6 +114,34 @@ export declare class CloudSyncService extends EventEmitter {
|
|
|
96
114
|
* Get machine ID
|
|
97
115
|
*/
|
|
98
116
|
getMachineIdentifier(): string;
|
|
117
|
+
/**
|
|
118
|
+
* Set the storage adapter for message sync
|
|
119
|
+
*/
|
|
120
|
+
setStorage(storage: StorageAdapter): void;
|
|
121
|
+
/**
|
|
122
|
+
* Queue a single message for sync to cloud.
|
|
123
|
+
* Use this for real-time sync as messages are created.
|
|
124
|
+
* Falls back to batch sync if optimized queue is not enabled.
|
|
125
|
+
*/
|
|
126
|
+
queueMessageForSync(message: StoredMessage): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Get sync queue statistics (if optimized sync is enabled).
|
|
129
|
+
*/
|
|
130
|
+
getSyncQueueStats(): SyncQueueStats | null;
|
|
131
|
+
/**
|
|
132
|
+
* Force flush the sync queue.
|
|
133
|
+
*/
|
|
134
|
+
flushSyncQueue(): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Sync local messages to cloud storage
|
|
137
|
+
*
|
|
138
|
+
* Reads messages from local SQLite since last sync and posts them
|
|
139
|
+
* to the cloud API for centralized storage and search.
|
|
140
|
+
*/
|
|
141
|
+
syncMessagesToCloud(): Promise<{
|
|
142
|
+
synced: number;
|
|
143
|
+
duplicates: number;
|
|
144
|
+
}>;
|
|
99
145
|
}
|
|
100
146
|
export declare function getCloudSync(config?: Partial<CloudSyncConfig>): CloudSyncService;
|
|
101
147
|
//# sourceMappingURL=cloud-sync.d.ts.map
|
|
@@ -13,6 +13,8 @@ import * as path from 'path';
|
|
|
13
13
|
import * as os from 'os';
|
|
14
14
|
import { randomBytes } from 'crypto';
|
|
15
15
|
import { createLogger } from '../utils/logger.js';
|
|
16
|
+
import { SyncQueue } from './sync-queue.js';
|
|
17
|
+
import { getRepoFullNameFromPath } from '../utils/git-remote.js';
|
|
16
18
|
const log = createLogger('cloud-sync');
|
|
17
19
|
export class CloudSyncService extends EventEmitter {
|
|
18
20
|
config;
|
|
@@ -21,16 +23,41 @@ export class CloudSyncService extends EventEmitter {
|
|
|
21
23
|
localAgents = new Map();
|
|
22
24
|
remoteAgents = [];
|
|
23
25
|
connected = false;
|
|
26
|
+
storage = null;
|
|
27
|
+
lastMessageSyncTs = 0;
|
|
28
|
+
messageSyncInProgress = false;
|
|
29
|
+
// Project context for workspace resolution
|
|
30
|
+
projectDirectory;
|
|
31
|
+
repoFullName = null;
|
|
32
|
+
// Optimized sync queue
|
|
33
|
+
syncQueue = null;
|
|
24
34
|
constructor(config = {}) {
|
|
25
35
|
super();
|
|
26
36
|
this.config = {
|
|
27
37
|
apiKey: config.apiKey || process.env.AGENT_RELAY_API_KEY,
|
|
28
|
-
cloudUrl: config.cloudUrl || process.env.AGENT_RELAY_CLOUD_URL || 'https://
|
|
38
|
+
cloudUrl: config.cloudUrl || process.env.AGENT_RELAY_CLOUD_URL || 'https://agent-relay.com',
|
|
29
39
|
heartbeatInterval: config.heartbeatInterval || 30000, // 30 seconds
|
|
30
40
|
enabled: config.enabled ?? true,
|
|
41
|
+
useOptimizedSync: config.useOptimizedSync ?? true,
|
|
42
|
+
syncQueue: config.syncQueue,
|
|
43
|
+
projectDirectory: config.projectDirectory,
|
|
31
44
|
};
|
|
32
45
|
// Generate or load machine ID for consistent identification
|
|
33
46
|
this.machineId = this.getMachineId();
|
|
47
|
+
// Initialize project context for workspace resolution
|
|
48
|
+
this.projectDirectory = this.config.projectDirectory || process.cwd();
|
|
49
|
+
this.repoFullName = getRepoFullNameFromPath(this.projectDirectory);
|
|
50
|
+
if (this.repoFullName) {
|
|
51
|
+
log.info('Detected git repository', { repoFullName: this.repoFullName });
|
|
52
|
+
}
|
|
53
|
+
// Initialize optimized sync queue if enabled and API key is available
|
|
54
|
+
if (this.config.useOptimizedSync && this.config.apiKey) {
|
|
55
|
+
this.syncQueue = new SyncQueue({
|
|
56
|
+
cloudUrl: this.config.cloudUrl,
|
|
57
|
+
apiKey: this.config.apiKey,
|
|
58
|
+
...this.config.syncQueue,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
34
61
|
}
|
|
35
62
|
/**
|
|
36
63
|
* Get or create a persistent machine ID
|
|
@@ -64,6 +91,13 @@ export class CloudSyncService extends EventEmitter {
|
|
|
64
91
|
return;
|
|
65
92
|
}
|
|
66
93
|
log.info('Starting cloud sync', { url: this.config.cloudUrl });
|
|
94
|
+
// Recover any spilled messages from previous runs
|
|
95
|
+
if (this.syncQueue) {
|
|
96
|
+
const { recovered, failed } = await this.syncQueue.recoverSpilledMessages();
|
|
97
|
+
if (recovered > 0 || failed > 0) {
|
|
98
|
+
log.info('Recovered spilled messages', { recovered, failed });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
67
101
|
// Initial heartbeat
|
|
68
102
|
await this.sendHeartbeat();
|
|
69
103
|
// Start periodic heartbeat
|
|
@@ -74,11 +108,15 @@ export class CloudSyncService extends EventEmitter {
|
|
|
74
108
|
/**
|
|
75
109
|
* Stop the cloud sync service
|
|
76
110
|
*/
|
|
77
|
-
stop() {
|
|
111
|
+
async stop() {
|
|
78
112
|
if (this.heartbeatTimer) {
|
|
79
113
|
clearInterval(this.heartbeatTimer);
|
|
80
114
|
this.heartbeatTimer = undefined;
|
|
81
115
|
}
|
|
116
|
+
// Gracefully close sync queue (flushes pending messages)
|
|
117
|
+
if (this.syncQueue) {
|
|
118
|
+
await this.syncQueue.close();
|
|
119
|
+
}
|
|
82
120
|
this.connected = false;
|
|
83
121
|
this.emit('disconnected');
|
|
84
122
|
}
|
|
@@ -167,10 +205,11 @@ export class CloudSyncService extends EventEmitter {
|
|
|
167
205
|
this.emit('command', cmd);
|
|
168
206
|
}
|
|
169
207
|
}
|
|
170
|
-
// Fetch messages and sync
|
|
208
|
+
// Fetch messages, sync agents, and sync local messages to cloud
|
|
171
209
|
await Promise.all([
|
|
172
210
|
this.fetchMessages(),
|
|
173
211
|
this.syncAgents(),
|
|
212
|
+
this.syncMessagesToCloud(),
|
|
174
213
|
]);
|
|
175
214
|
}
|
|
176
215
|
catch (error) {
|
|
@@ -251,6 +290,116 @@ export class CloudSyncService extends EventEmitter {
|
|
|
251
290
|
getMachineIdentifier() {
|
|
252
291
|
return this.machineId;
|
|
253
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Set the storage adapter for message sync
|
|
295
|
+
*/
|
|
296
|
+
setStorage(storage) {
|
|
297
|
+
this.storage = storage;
|
|
298
|
+
log.info('Storage adapter configured for message sync');
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Queue a single message for sync to cloud.
|
|
302
|
+
* Use this for real-time sync as messages are created.
|
|
303
|
+
* Falls back to batch sync if optimized queue is not enabled.
|
|
304
|
+
*/
|
|
305
|
+
async queueMessageForSync(message) {
|
|
306
|
+
if (!this.connected || this.config.messageSyncEnabled === false) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (this.syncQueue) {
|
|
310
|
+
await this.syncQueue.enqueue(message);
|
|
311
|
+
}
|
|
312
|
+
// If no sync queue, messages will be synced on next heartbeat via syncMessagesToCloud
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get sync queue statistics (if optimized sync is enabled).
|
|
316
|
+
*/
|
|
317
|
+
getSyncQueueStats() {
|
|
318
|
+
return this.syncQueue?.getStats() ?? null;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Force flush the sync queue.
|
|
322
|
+
*/
|
|
323
|
+
async flushSyncQueue() {
|
|
324
|
+
if (this.syncQueue) {
|
|
325
|
+
await this.syncQueue.flush();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Sync local messages to cloud storage
|
|
330
|
+
*
|
|
331
|
+
* Reads messages from local SQLite since last sync and posts them
|
|
332
|
+
* to the cloud API for centralized storage and search.
|
|
333
|
+
*/
|
|
334
|
+
async syncMessagesToCloud() {
|
|
335
|
+
// Skip if disabled, not connected, no storage, or sync in progress
|
|
336
|
+
if (!this.connected || !this.storage || this.messageSyncInProgress) {
|
|
337
|
+
return { synced: 0, duplicates: 0 };
|
|
338
|
+
}
|
|
339
|
+
if (this.config.messageSyncEnabled === false) {
|
|
340
|
+
return { synced: 0, duplicates: 0 };
|
|
341
|
+
}
|
|
342
|
+
this.messageSyncInProgress = true;
|
|
343
|
+
try {
|
|
344
|
+
const batchSize = this.config.messageSyncBatchSize || 100;
|
|
345
|
+
// Get messages since last sync
|
|
346
|
+
const messages = await this.storage.getMessages({
|
|
347
|
+
sinceTs: this.lastMessageSyncTs > 0 ? this.lastMessageSyncTs : undefined,
|
|
348
|
+
limit: batchSize,
|
|
349
|
+
order: 'asc',
|
|
350
|
+
});
|
|
351
|
+
if (messages.length === 0) {
|
|
352
|
+
return { synced: 0, duplicates: 0 };
|
|
353
|
+
}
|
|
354
|
+
// Transform to API format
|
|
355
|
+
const syncPayload = messages.map((msg) => ({
|
|
356
|
+
id: msg.id,
|
|
357
|
+
ts: msg.ts,
|
|
358
|
+
from: msg.from,
|
|
359
|
+
to: msg.to,
|
|
360
|
+
body: msg.body,
|
|
361
|
+
kind: msg.kind,
|
|
362
|
+
topic: msg.topic,
|
|
363
|
+
thread: msg.thread,
|
|
364
|
+
is_broadcast: msg.is_broadcast,
|
|
365
|
+
is_urgent: msg.is_urgent,
|
|
366
|
+
data: msg.data,
|
|
367
|
+
payload_meta: msg.payloadMeta,
|
|
368
|
+
}));
|
|
369
|
+
// Post to cloud with repo context for workspace resolution
|
|
370
|
+
const response = await fetch(`${this.config.cloudUrl}/api/daemons/messages/sync`, {
|
|
371
|
+
method: 'POST',
|
|
372
|
+
headers: {
|
|
373
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
374
|
+
'Content-Type': 'application/json',
|
|
375
|
+
},
|
|
376
|
+
body: JSON.stringify({
|
|
377
|
+
messages: syncPayload,
|
|
378
|
+
repoFullName: this.repoFullName,
|
|
379
|
+
}),
|
|
380
|
+
});
|
|
381
|
+
if (!response.ok) {
|
|
382
|
+
const errorText = await response.text();
|
|
383
|
+
throw new Error(`Message sync failed: ${response.status} - ${errorText}`);
|
|
384
|
+
}
|
|
385
|
+
const result = await response.json();
|
|
386
|
+
// Update last sync timestamp to the newest message we synced
|
|
387
|
+
if (messages.length > 0) {
|
|
388
|
+
this.lastMessageSyncTs = Math.max(...messages.map((m) => m.ts));
|
|
389
|
+
}
|
|
390
|
+
if (result.synced > 0) {
|
|
391
|
+
log.info(`Synced ${result.synced} messages to cloud`, { duplicates: result.duplicates });
|
|
392
|
+
}
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
log.error('Message sync error', { error: String(error) });
|
|
397
|
+
return { synced: 0, duplicates: 0 };
|
|
398
|
+
}
|
|
399
|
+
finally {
|
|
400
|
+
this.messageSyncInProgress = false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
254
403
|
}
|
|
255
404
|
// Singleton instance
|
|
256
405
|
let _cloudSync = null;
|
|
@@ -30,6 +30,12 @@ export interface ConnectionConfig {
|
|
|
30
30
|
} | null>;
|
|
31
31
|
/** Optional callback to check if agent is currently processing (exempts from heartbeat timeout) */
|
|
32
32
|
isProcessing?: (agentName: string) => boolean;
|
|
33
|
+
/** Maximum messages in write queue before dropping (default: 2000) */
|
|
34
|
+
maxWriteQueueSize?: number;
|
|
35
|
+
/** High water mark - emit backpressure signal when queue exceeds this (default: 1500) */
|
|
36
|
+
writeQueueHighWaterMark?: number;
|
|
37
|
+
/** Low water mark - release backpressure when queue drops below this (default: 500) */
|
|
38
|
+
writeQueueLowWaterMark?: number;
|
|
33
39
|
}
|
|
34
40
|
export declare const DEFAULT_CONFIG: ConnectionConfig;
|
|
35
41
|
export declare class Connection {
|
|
@@ -53,12 +59,18 @@ export declare class Connection {
|
|
|
53
59
|
private heartbeatTimer?;
|
|
54
60
|
private lastPongReceived?;
|
|
55
61
|
private sequences;
|
|
62
|
+
private writeQueue;
|
|
63
|
+
private draining;
|
|
64
|
+
private _backpressured;
|
|
65
|
+
private socketDrainHandler?;
|
|
56
66
|
onMessage?: (envelope: Envelope) => void;
|
|
57
67
|
onClose?: () => void;
|
|
58
68
|
onError?: (error: Error) => void;
|
|
59
69
|
onActive?: () => void;
|
|
60
70
|
onAck?: (envelope: Envelope<AckPayload>) => void;
|
|
61
71
|
onPong?: () => void;
|
|
72
|
+
/** Fires when write queue crosses high/low water marks */
|
|
73
|
+
onBackpressure?: (backpressured: boolean) => void;
|
|
62
74
|
constructor(socket: net.Socket, config?: Partial<ConnectionConfig>);
|
|
63
75
|
get state(): ConnectionState;
|
|
64
76
|
get agentName(): string | undefined;
|
|
@@ -73,6 +85,10 @@ export declare class Connection {
|
|
|
73
85
|
get sessionId(): string;
|
|
74
86
|
get resumeToken(): string;
|
|
75
87
|
get isResumed(): boolean;
|
|
88
|
+
/** Whether this connection is currently backpressured (write queue above high water mark) */
|
|
89
|
+
get backpressured(): boolean;
|
|
90
|
+
/** Current number of messages queued for writing */
|
|
91
|
+
get writeQueueLength(): number;
|
|
76
92
|
private setupSocketHandlers;
|
|
77
93
|
private handleData;
|
|
78
94
|
private processFrame;
|
|
@@ -91,8 +107,20 @@ export declare class Connection {
|
|
|
91
107
|
getNextSeq(topic: string, peer: string): number;
|
|
92
108
|
/**
|
|
93
109
|
* Send an envelope to this connection.
|
|
110
|
+
*
|
|
111
|
+
* Uses a write queue to prevent blocking on slow consumers.
|
|
112
|
+
* Returns false if the connection is closed or the queue is full.
|
|
94
113
|
*/
|
|
95
114
|
send(envelope: Envelope): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Schedule the drain loop to run on next tick if not already running.
|
|
117
|
+
*/
|
|
118
|
+
private scheduleDrain;
|
|
119
|
+
/**
|
|
120
|
+
* Drain the write queue to the socket.
|
|
121
|
+
* Respects socket backpressure by waiting for 'drain' events.
|
|
122
|
+
*/
|
|
123
|
+
private drain;
|
|
96
124
|
private sendError;
|
|
97
125
|
private handleClose;
|
|
98
126
|
private handleError;
|