agent-relay 2.0.23 → 2.0.24
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/dist/src/cli/index.js +66 -13
- package/package.json +18 -52
- package/packages/api-types/package.json +1 -1
- package/packages/bridge/package.json +8 -8
- package/packages/cli-tester/package.json +1 -1
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/package.json +6 -6
- package/deploy/init-db.sql +0 -5
- package/deploy/scripts/setup-fly-workspaces.sh +0 -69
- package/deploy/scripts/setup-railway.sh +0 -75
- package/dist/src/cloud/index.d.ts +0 -8
- package/dist/src/cloud/index.js +0 -8
- package/packages/cloud/dist/api/admin.d.ts +0 -8
- package/packages/cloud/dist/api/admin.js +0 -225
- package/packages/cloud/dist/api/auth.d.ts +0 -20
- package/packages/cloud/dist/api/auth.js +0 -138
- package/packages/cloud/dist/api/billing.d.ts +0 -7
- package/packages/cloud/dist/api/billing.js +0 -564
- package/packages/cloud/dist/api/cli-pty-runner.d.ts +0 -53
- package/packages/cloud/dist/api/cli-pty-runner.js +0 -175
- package/packages/cloud/dist/api/codex-auth-helper.d.ts +0 -21
- package/packages/cloud/dist/api/codex-auth-helper.js +0 -327
- package/packages/cloud/dist/api/consensus.d.ts +0 -13
- package/packages/cloud/dist/api/consensus.js +0 -261
- package/packages/cloud/dist/api/coordinators.d.ts +0 -8
- package/packages/cloud/dist/api/coordinators.js +0 -750
- package/packages/cloud/dist/api/daemons.d.ts +0 -12
- package/packages/cloud/dist/api/daemons.js +0 -535
- package/packages/cloud/dist/api/email-auth.d.ts +0 -11
- package/packages/cloud/dist/api/email-auth.js +0 -347
- package/packages/cloud/dist/api/generic-webhooks.d.ts +0 -8
- package/packages/cloud/dist/api/generic-webhooks.js +0 -129
- package/packages/cloud/dist/api/git.d.ts +0 -8
- package/packages/cloud/dist/api/git.js +0 -269
- package/packages/cloud/dist/api/github-app.d.ts +0 -11
- package/packages/cloud/dist/api/github-app.js +0 -223
- package/packages/cloud/dist/api/middleware/planLimits.d.ts +0 -43
- package/packages/cloud/dist/api/middleware/planLimits.js +0 -202
- package/packages/cloud/dist/api/monitoring.d.ts +0 -11
- package/packages/cloud/dist/api/monitoring.js +0 -578
- package/packages/cloud/dist/api/nango-auth.d.ts +0 -9
- package/packages/cloud/dist/api/nango-auth.js +0 -741
- package/packages/cloud/dist/api/onboarding.d.ts +0 -15
- package/packages/cloud/dist/api/onboarding.js +0 -679
- package/packages/cloud/dist/api/policy.d.ts +0 -8
- package/packages/cloud/dist/api/policy.js +0 -229
- package/packages/cloud/dist/api/provider-env.d.ts +0 -26
- package/packages/cloud/dist/api/provider-env.js +0 -141
- package/packages/cloud/dist/api/providers.d.ts +0 -7
- package/packages/cloud/dist/api/providers.js +0 -574
- package/packages/cloud/dist/api/repos.d.ts +0 -8
- package/packages/cloud/dist/api/repos.js +0 -577
- package/packages/cloud/dist/api/sessions.d.ts +0 -11
- package/packages/cloud/dist/api/sessions.js +0 -302
- package/packages/cloud/dist/api/teams.d.ts +0 -7
- package/packages/cloud/dist/api/teams.js +0 -281
- package/packages/cloud/dist/api/test-helpers.d.ts +0 -10
- package/packages/cloud/dist/api/test-helpers.js +0 -745
- package/packages/cloud/dist/api/usage.d.ts +0 -7
- package/packages/cloud/dist/api/usage.js +0 -111
- package/packages/cloud/dist/api/webhooks.d.ts +0 -8
- package/packages/cloud/dist/api/webhooks.js +0 -645
- package/packages/cloud/dist/api/workspaces.d.ts +0 -25
- package/packages/cloud/dist/api/workspaces.js +0 -1799
- package/packages/cloud/dist/billing/index.d.ts +0 -9
- package/packages/cloud/dist/billing/index.js +0 -9
- package/packages/cloud/dist/billing/plans.d.ts +0 -39
- package/packages/cloud/dist/billing/plans.js +0 -245
- package/packages/cloud/dist/billing/service.d.ts +0 -80
- package/packages/cloud/dist/billing/service.js +0 -388
- package/packages/cloud/dist/billing/types.d.ts +0 -141
- package/packages/cloud/dist/billing/types.js +0 -7
- package/packages/cloud/dist/config.d.ts +0 -5
- package/packages/cloud/dist/config.js +0 -5
- package/packages/cloud/dist/db/bulk-ingest.d.ts +0 -89
- package/packages/cloud/dist/db/bulk-ingest.js +0 -268
- package/packages/cloud/dist/db/drizzle.d.ts +0 -290
- package/packages/cloud/dist/db/drizzle.js +0 -1422
- package/packages/cloud/dist/db/index.d.ts +0 -56
- package/packages/cloud/dist/db/index.js +0 -70
- package/packages/cloud/dist/db/schema.d.ts +0 -5117
- package/packages/cloud/dist/db/schema.js +0 -656
- package/packages/cloud/dist/index.d.ts +0 -11
- package/packages/cloud/dist/index.js +0 -38
- package/packages/cloud/dist/provisioner/index.d.ts +0 -207
- package/packages/cloud/dist/provisioner/index.js +0 -2118
- package/packages/cloud/dist/server.d.ts +0 -17
- package/packages/cloud/dist/server.js +0 -2055
- package/packages/cloud/dist/services/auto-scaler.d.ts +0 -152
- package/packages/cloud/dist/services/auto-scaler.js +0 -439
- package/packages/cloud/dist/services/capacity-manager.d.ts +0 -148
- package/packages/cloud/dist/services/capacity-manager.js +0 -449
- package/packages/cloud/dist/services/ci-agent-spawner.d.ts +0 -49
- package/packages/cloud/dist/services/ci-agent-spawner.js +0 -373
- package/packages/cloud/dist/services/cloud-message-bus.d.ts +0 -28
- package/packages/cloud/dist/services/cloud-message-bus.js +0 -19
- package/packages/cloud/dist/services/compute-enforcement.d.ts +0 -57
- package/packages/cloud/dist/services/compute-enforcement.js +0 -175
- package/packages/cloud/dist/services/coordinator.d.ts +0 -62
- package/packages/cloud/dist/services/coordinator.js +0 -389
- package/packages/cloud/dist/services/index.d.ts +0 -17
- package/packages/cloud/dist/services/index.js +0 -25
- package/packages/cloud/dist/services/intro-expiration.d.ts +0 -60
- package/packages/cloud/dist/services/intro-expiration.js +0 -252
- package/packages/cloud/dist/services/mention-handler.d.ts +0 -65
- package/packages/cloud/dist/services/mention-handler.js +0 -405
- package/packages/cloud/dist/services/nango.d.ts +0 -219
- package/packages/cloud/dist/services/nango.js +0 -424
- package/packages/cloud/dist/services/persistence.d.ts +0 -131
- package/packages/cloud/dist/services/persistence.js +0 -200
- package/packages/cloud/dist/services/planLimits.d.ts +0 -147
- package/packages/cloud/dist/services/planLimits.js +0 -335
- package/packages/cloud/dist/services/presence-registry.d.ts +0 -56
- package/packages/cloud/dist/services/presence-registry.js +0 -91
- package/packages/cloud/dist/services/scaling-orchestrator.d.ts +0 -159
- package/packages/cloud/dist/services/scaling-orchestrator.js +0 -502
- package/packages/cloud/dist/services/scaling-policy.d.ts +0 -121
- package/packages/cloud/dist/services/scaling-policy.js +0 -415
- package/packages/cloud/dist/services/ssh-security.d.ts +0 -31
- package/packages/cloud/dist/services/ssh-security.js +0 -63
- package/packages/cloud/dist/services/workspace-keepalive.d.ts +0 -76
- package/packages/cloud/dist/services/workspace-keepalive.js +0 -234
- package/packages/cloud/dist/shims/consensus.d.ts +0 -23
- package/packages/cloud/dist/shims/consensus.js +0 -5
- package/packages/cloud/dist/webhooks/index.d.ts +0 -24
- package/packages/cloud/dist/webhooks/index.js +0 -29
- package/packages/cloud/dist/webhooks/parsers/github.d.ts +0 -8
- package/packages/cloud/dist/webhooks/parsers/github.js +0 -234
- package/packages/cloud/dist/webhooks/parsers/index.d.ts +0 -23
- package/packages/cloud/dist/webhooks/parsers/index.js +0 -30
- package/packages/cloud/dist/webhooks/parsers/linear.d.ts +0 -9
- package/packages/cloud/dist/webhooks/parsers/linear.js +0 -258
- package/packages/cloud/dist/webhooks/parsers/slack.d.ts +0 -9
- package/packages/cloud/dist/webhooks/parsers/slack.js +0 -214
- package/packages/cloud/dist/webhooks/responders/github.d.ts +0 -8
- package/packages/cloud/dist/webhooks/responders/github.js +0 -73
- package/packages/cloud/dist/webhooks/responders/index.d.ts +0 -23
- package/packages/cloud/dist/webhooks/responders/index.js +0 -30
- package/packages/cloud/dist/webhooks/responders/linear.d.ts +0 -9
- package/packages/cloud/dist/webhooks/responders/linear.js +0 -149
- package/packages/cloud/dist/webhooks/responders/slack.d.ts +0 -20
- package/packages/cloud/dist/webhooks/responders/slack.js +0 -178
- package/packages/cloud/dist/webhooks/router.d.ts +0 -25
- package/packages/cloud/dist/webhooks/router.js +0 -504
- package/packages/cloud/dist/webhooks/rules-engine.d.ts +0 -24
- package/packages/cloud/dist/webhooks/rules-engine.js +0 -287
- package/packages/cloud/dist/webhooks/types.d.ts +0 -186
- package/packages/cloud/dist/webhooks/types.js +0 -8
- package/packages/cloud/package.json +0 -60
- package/scripts/run-migrations.js +0 -43
- package/scripts/setup-stripe-products.ts +0 -312
- package/scripts/verify-schema.js +0 -134
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-Scaler Service
|
|
3
|
-
*
|
|
4
|
-
* Monitors workspace metrics and automatically scales instances based on
|
|
5
|
-
* defined policies. Uses Redis pub/sub for cross-server coordination to
|
|
6
|
-
* ensure only one scaling operation happens at a time.
|
|
7
|
-
*
|
|
8
|
-
* Key responsibilities:
|
|
9
|
-
* - Subscribe to metrics updates from monitoring service
|
|
10
|
-
* - Evaluate scaling policies periodically
|
|
11
|
-
* - Coordinate scaling decisions across multiple cloud servers
|
|
12
|
-
* - Execute scaling actions via workspace provisioner
|
|
13
|
-
* - Track scaling history and pending operations
|
|
14
|
-
*/
|
|
15
|
-
import { EventEmitter } from 'events';
|
|
16
|
-
import { ScalingDecision, UserScalingContext, WorkspaceMetrics } from './scaling-policy.js';
|
|
17
|
-
export interface ScalingOperation {
|
|
18
|
-
id: string;
|
|
19
|
-
userId: string;
|
|
20
|
-
action: 'scale_up' | 'scale_down' | 'resize_up' | 'resize_down' | 'increase_agent_limit' | 'migrate_agents' | 'rebalance';
|
|
21
|
-
targetWorkspaceId?: string;
|
|
22
|
-
targetResourceTier?: 'small' | 'medium' | 'large' | 'xlarge';
|
|
23
|
-
targetAgentLimit?: number;
|
|
24
|
-
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
|
25
|
-
startedAt: Date;
|
|
26
|
-
completedAt?: Date;
|
|
27
|
-
error?: string;
|
|
28
|
-
triggeredBy: string;
|
|
29
|
-
metrics: Record<string, number>;
|
|
30
|
-
}
|
|
31
|
-
export interface AutoScalerConfig {
|
|
32
|
-
enabled: boolean;
|
|
33
|
-
evaluationIntervalMs: number;
|
|
34
|
-
lockTimeoutMs: number;
|
|
35
|
-
maxConcurrentOperations: number;
|
|
36
|
-
redisKeyPrefix: string;
|
|
37
|
-
}
|
|
38
|
-
export interface MetricsSnapshot {
|
|
39
|
-
userId: string;
|
|
40
|
-
workspaces: WorkspaceMetrics[];
|
|
41
|
-
timestamp: Date;
|
|
42
|
-
}
|
|
43
|
-
export declare class AutoScaler extends EventEmitter {
|
|
44
|
-
private config;
|
|
45
|
-
private policyService;
|
|
46
|
-
private redis;
|
|
47
|
-
private subscriber;
|
|
48
|
-
private evaluationTimer;
|
|
49
|
-
private pendingOperations;
|
|
50
|
-
private metricsCache;
|
|
51
|
-
private isLeader;
|
|
52
|
-
private serverId;
|
|
53
|
-
private lastScalingActions;
|
|
54
|
-
constructor(config?: Partial<AutoScalerConfig>);
|
|
55
|
-
/**
|
|
56
|
-
* Initialize with Redis connection for cross-server coordination
|
|
57
|
-
*/
|
|
58
|
-
initialize(redisUrl: string): Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
* Set up Redis pub/sub subscriptions
|
|
61
|
-
*/
|
|
62
|
-
private setupSubscriptions;
|
|
63
|
-
/**
|
|
64
|
-
* Handle channel message
|
|
65
|
-
*/
|
|
66
|
-
private handleChannelMessage;
|
|
67
|
-
/**
|
|
68
|
-
* Handle incoming pub/sub messages
|
|
69
|
-
*/
|
|
70
|
-
private handlePubSubMessage;
|
|
71
|
-
/**
|
|
72
|
-
* Handle metrics update from monitoring service
|
|
73
|
-
*/
|
|
74
|
-
private handleMetricsUpdate;
|
|
75
|
-
/**
|
|
76
|
-
* Handle scaling request (from any server)
|
|
77
|
-
*/
|
|
78
|
-
private handleScalingRequest;
|
|
79
|
-
/**
|
|
80
|
-
* Handle scaling completion
|
|
81
|
-
*/
|
|
82
|
-
private handleScalingComplete;
|
|
83
|
-
/**
|
|
84
|
-
* Handle leadership change
|
|
85
|
-
*/
|
|
86
|
-
private handleLeadershipChange;
|
|
87
|
-
/**
|
|
88
|
-
* Attempt to become the leader (only leader evaluates scaling)
|
|
89
|
-
*/
|
|
90
|
-
private attemptLeadership;
|
|
91
|
-
/**
|
|
92
|
-
* Schedule leadership lock renewal
|
|
93
|
-
*/
|
|
94
|
-
private scheduleLeadershipRenewal;
|
|
95
|
-
/**
|
|
96
|
-
* Start the periodic evaluation loop
|
|
97
|
-
*/
|
|
98
|
-
private startEvaluationLoop;
|
|
99
|
-
/**
|
|
100
|
-
* Evaluate scaling for all cached users
|
|
101
|
-
*/
|
|
102
|
-
private evaluateAllUsers;
|
|
103
|
-
/**
|
|
104
|
-
* Evaluate scaling for a specific user
|
|
105
|
-
*/
|
|
106
|
-
private evaluateUserScaling;
|
|
107
|
-
/**
|
|
108
|
-
* Build user context for policy evaluation
|
|
109
|
-
*/
|
|
110
|
-
private buildUserContext;
|
|
111
|
-
/**
|
|
112
|
-
* Request a scaling operation
|
|
113
|
-
*/
|
|
114
|
-
private requestScaling;
|
|
115
|
-
/**
|
|
116
|
-
* Execute the actual scaling operation
|
|
117
|
-
*/
|
|
118
|
-
private executeScaling;
|
|
119
|
-
/**
|
|
120
|
-
* Report metrics from monitoring service
|
|
121
|
-
*/
|
|
122
|
-
reportMetrics(userId: string, workspaces: WorkspaceMetrics[]): Promise<void>;
|
|
123
|
-
/**
|
|
124
|
-
* Manually trigger scaling evaluation for a user
|
|
125
|
-
*/
|
|
126
|
-
triggerEvaluation(userId: string): Promise<ScalingDecision | null>;
|
|
127
|
-
/**
|
|
128
|
-
* Get current scaling status
|
|
129
|
-
*/
|
|
130
|
-
getStatus(): {
|
|
131
|
-
enabled: boolean;
|
|
132
|
-
isLeader: boolean;
|
|
133
|
-
serverId: string;
|
|
134
|
-
pendingOperations: number;
|
|
135
|
-
cachedUsers: number;
|
|
136
|
-
};
|
|
137
|
-
/**
|
|
138
|
-
* Get pending operations for a user
|
|
139
|
-
*/
|
|
140
|
-
getPendingOperations(userId?: string): ScalingOperation[];
|
|
141
|
-
/**
|
|
142
|
-
* Update user plan in cache
|
|
143
|
-
*/
|
|
144
|
-
setUserPlan(userId: string, plan: UserScalingContext['plan']): Promise<void>;
|
|
145
|
-
/**
|
|
146
|
-
* Clean shutdown
|
|
147
|
-
*/
|
|
148
|
-
shutdown(): Promise<void>;
|
|
149
|
-
}
|
|
150
|
-
export declare function getAutoScaler(): AutoScaler;
|
|
151
|
-
export declare function createAutoScaler(config?: Partial<AutoScalerConfig>): AutoScaler;
|
|
152
|
-
//# sourceMappingURL=auto-scaler.d.ts.map
|
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-Scaler Service
|
|
3
|
-
*
|
|
4
|
-
* Monitors workspace metrics and automatically scales instances based on
|
|
5
|
-
* defined policies. Uses Redis pub/sub for cross-server coordination to
|
|
6
|
-
* ensure only one scaling operation happens at a time.
|
|
7
|
-
*
|
|
8
|
-
* Key responsibilities:
|
|
9
|
-
* - Subscribe to metrics updates from monitoring service
|
|
10
|
-
* - Evaluate scaling policies periodically
|
|
11
|
-
* - Coordinate scaling decisions across multiple cloud servers
|
|
12
|
-
* - Execute scaling actions via workspace provisioner
|
|
13
|
-
* - Track scaling history and pending operations
|
|
14
|
-
*/
|
|
15
|
-
import { EventEmitter } from 'events';
|
|
16
|
-
import { createClient } from 'redis';
|
|
17
|
-
import { getScalingPolicyService, } from './scaling-policy.js';
|
|
18
|
-
const DEFAULT_CONFIG = {
|
|
19
|
-
enabled: true,
|
|
20
|
-
evaluationIntervalMs: 30000, // 30 seconds
|
|
21
|
-
lockTimeoutMs: 60000, // 1 minute
|
|
22
|
-
maxConcurrentOperations: 5,
|
|
23
|
-
redisKeyPrefix: 'autoscaler:',
|
|
24
|
-
};
|
|
25
|
-
// Redis pub/sub channels
|
|
26
|
-
const CHANNELS = {
|
|
27
|
-
METRICS_UPDATE: 'autoscaler:metrics',
|
|
28
|
-
SCALING_REQUEST: 'autoscaler:scale',
|
|
29
|
-
SCALING_COMPLETE: 'autoscaler:complete',
|
|
30
|
-
LOCK_ACQUIRED: 'autoscaler:lock',
|
|
31
|
-
};
|
|
32
|
-
export class AutoScaler extends EventEmitter {
|
|
33
|
-
config;
|
|
34
|
-
policyService;
|
|
35
|
-
redis = null;
|
|
36
|
-
subscriber = null;
|
|
37
|
-
evaluationTimer = null;
|
|
38
|
-
pendingOperations = new Map();
|
|
39
|
-
metricsCache = new Map();
|
|
40
|
-
isLeader = false;
|
|
41
|
-
serverId;
|
|
42
|
-
lastScalingActions = new Map(); // userId -> lastAction
|
|
43
|
-
constructor(config = {}) {
|
|
44
|
-
super();
|
|
45
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
46
|
-
this.policyService = getScalingPolicyService();
|
|
47
|
-
this.serverId = `server-${process.pid}-${Date.now()}`;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Initialize with Redis connection for cross-server coordination
|
|
51
|
-
*/
|
|
52
|
-
async initialize(redisUrl) {
|
|
53
|
-
if (!this.config.enabled) {
|
|
54
|
-
this.emit('disabled');
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
// Main Redis client for commands
|
|
59
|
-
this.redis = createClient({ url: redisUrl });
|
|
60
|
-
this.redis.on('error', (err) => this.emit('error', { context: 'redis', error: err }));
|
|
61
|
-
// Separate client for subscriptions
|
|
62
|
-
this.subscriber = createClient({ url: redisUrl });
|
|
63
|
-
this.subscriber.on('error', (err) => this.emit('error', { context: 'subscriber', error: err }));
|
|
64
|
-
await Promise.all([this.redis.connect(), this.subscriber.connect()]);
|
|
65
|
-
// Set up pub/sub subscriptions
|
|
66
|
-
await this.setupSubscriptions();
|
|
67
|
-
// Start evaluation loop
|
|
68
|
-
this.startEvaluationLoop();
|
|
69
|
-
// Attempt to become leader
|
|
70
|
-
await this.attemptLeadership();
|
|
71
|
-
this.emit('initialized', { serverId: this.serverId, isLeader: this.isLeader });
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
this.emit('error', error);
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Set up Redis pub/sub subscriptions
|
|
80
|
-
*/
|
|
81
|
-
async setupSubscriptions() {
|
|
82
|
-
if (!this.subscriber)
|
|
83
|
-
return;
|
|
84
|
-
// Subscribe to all channels
|
|
85
|
-
await this.subscriber.subscribe(CHANNELS.METRICS_UPDATE, (message) => {
|
|
86
|
-
this.handleChannelMessage(CHANNELS.METRICS_UPDATE, message);
|
|
87
|
-
});
|
|
88
|
-
await this.subscriber.subscribe(CHANNELS.SCALING_REQUEST, (message) => {
|
|
89
|
-
this.handleChannelMessage(CHANNELS.SCALING_REQUEST, message);
|
|
90
|
-
});
|
|
91
|
-
await this.subscriber.subscribe(CHANNELS.SCALING_COMPLETE, (message) => {
|
|
92
|
-
this.handleChannelMessage(CHANNELS.SCALING_COMPLETE, message);
|
|
93
|
-
});
|
|
94
|
-
await this.subscriber.subscribe(CHANNELS.LOCK_ACQUIRED, (message) => {
|
|
95
|
-
this.handleChannelMessage(CHANNELS.LOCK_ACQUIRED, message);
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Handle channel message
|
|
100
|
-
*/
|
|
101
|
-
handleChannelMessage(channel, message) {
|
|
102
|
-
try {
|
|
103
|
-
const data = JSON.parse(message);
|
|
104
|
-
this.handlePubSubMessage(channel, data).catch((err) => {
|
|
105
|
-
this.emit('error', { context: 'message_handler', error: err });
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
this.emit('error', { context: 'pubsub_parse', error });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Handle incoming pub/sub messages
|
|
114
|
-
*/
|
|
115
|
-
async handlePubSubMessage(channel, data) {
|
|
116
|
-
switch (channel) {
|
|
117
|
-
case CHANNELS.METRICS_UPDATE:
|
|
118
|
-
await this.handleMetricsUpdate(data);
|
|
119
|
-
break;
|
|
120
|
-
case CHANNELS.SCALING_REQUEST:
|
|
121
|
-
await this.handleScalingRequest(data);
|
|
122
|
-
break;
|
|
123
|
-
case CHANNELS.SCALING_COMPLETE:
|
|
124
|
-
await this.handleScalingComplete(data);
|
|
125
|
-
break;
|
|
126
|
-
case CHANNELS.LOCK_ACQUIRED:
|
|
127
|
-
this.handleLeadershipChange(data);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Handle metrics update from monitoring service
|
|
133
|
-
*/
|
|
134
|
-
async handleMetricsUpdate(snapshot) {
|
|
135
|
-
this.metricsCache.set(snapshot.userId, snapshot);
|
|
136
|
-
this.emit('metrics_received', snapshot);
|
|
137
|
-
// If we're the leader, evaluate immediately for this user
|
|
138
|
-
if (this.isLeader) {
|
|
139
|
-
await this.evaluateUserScaling(snapshot.userId);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Handle scaling request (from any server)
|
|
144
|
-
*/
|
|
145
|
-
async handleScalingRequest(operation) {
|
|
146
|
-
// Track the operation
|
|
147
|
-
this.pendingOperations.set(operation.id, operation);
|
|
148
|
-
this.emit('scaling_started', operation);
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Handle scaling completion
|
|
152
|
-
*/
|
|
153
|
-
async handleScalingComplete(operation) {
|
|
154
|
-
const pending = this.pendingOperations.get(operation.id);
|
|
155
|
-
if (pending) {
|
|
156
|
-
this.pendingOperations.delete(operation.id);
|
|
157
|
-
this.lastScalingActions.set(operation.userId, new Date());
|
|
158
|
-
}
|
|
159
|
-
this.emit('scaling_completed', operation);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Handle leadership change
|
|
163
|
-
*/
|
|
164
|
-
handleLeadershipChange(data) {
|
|
165
|
-
if (data.serverId !== this.serverId) {
|
|
166
|
-
this.isLeader = false;
|
|
167
|
-
this.emit('leadership_lost');
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Attempt to become the leader (only leader evaluates scaling)
|
|
172
|
-
*/
|
|
173
|
-
async attemptLeadership() {
|
|
174
|
-
if (!this.redis)
|
|
175
|
-
return false;
|
|
176
|
-
const lockKey = `${this.config.redisKeyPrefix}leader`;
|
|
177
|
-
const result = await this.redis.set(lockKey, this.serverId, {
|
|
178
|
-
PX: this.config.lockTimeoutMs,
|
|
179
|
-
NX: true,
|
|
180
|
-
});
|
|
181
|
-
if (result === 'OK') {
|
|
182
|
-
this.isLeader = true;
|
|
183
|
-
await this.redis.publish(CHANNELS.LOCK_ACQUIRED, JSON.stringify({ serverId: this.serverId }));
|
|
184
|
-
this.emit('became_leader');
|
|
185
|
-
// Renew leadership periodically
|
|
186
|
-
this.scheduleLeadershipRenewal();
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Schedule leadership lock renewal
|
|
193
|
-
*/
|
|
194
|
-
scheduleLeadershipRenewal() {
|
|
195
|
-
const renewInterval = this.config.lockTimeoutMs / 2;
|
|
196
|
-
setInterval(async () => {
|
|
197
|
-
if (this.isLeader && this.redis) {
|
|
198
|
-
const lockKey = `${this.config.redisKeyPrefix}leader`;
|
|
199
|
-
const currentHolder = await this.redis.get(lockKey);
|
|
200
|
-
if (currentHolder === this.serverId) {
|
|
201
|
-
await this.redis.pExpire(lockKey, this.config.lockTimeoutMs);
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
this.isLeader = false;
|
|
205
|
-
this.emit('leadership_lost');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}, renewInterval);
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Start the periodic evaluation loop
|
|
212
|
-
*/
|
|
213
|
-
startEvaluationLoop() {
|
|
214
|
-
if (this.evaluationTimer) {
|
|
215
|
-
clearInterval(this.evaluationTimer);
|
|
216
|
-
}
|
|
217
|
-
this.evaluationTimer = setInterval(async () => {
|
|
218
|
-
if (this.isLeader) {
|
|
219
|
-
await this.evaluateAllUsers();
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
// Try to become leader if current leader is gone
|
|
223
|
-
await this.attemptLeadership();
|
|
224
|
-
}
|
|
225
|
-
}, this.config.evaluationIntervalMs);
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Evaluate scaling for all cached users
|
|
229
|
-
*/
|
|
230
|
-
async evaluateAllUsers() {
|
|
231
|
-
const evaluations = [];
|
|
232
|
-
for (const userId of this.metricsCache.keys()) {
|
|
233
|
-
evaluations.push(this.evaluateUserScaling(userId));
|
|
234
|
-
}
|
|
235
|
-
await Promise.allSettled(evaluations);
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Evaluate scaling for a specific user
|
|
239
|
-
*/
|
|
240
|
-
async evaluateUserScaling(userId) {
|
|
241
|
-
const snapshot = this.metricsCache.get(userId);
|
|
242
|
-
if (!snapshot)
|
|
243
|
-
return;
|
|
244
|
-
// Check if we have too many pending operations
|
|
245
|
-
const userPendingOps = Array.from(this.pendingOperations.values()).filter((op) => op.userId === userId && op.status === 'in_progress').length;
|
|
246
|
-
if (userPendingOps >= this.config.maxConcurrentOperations) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
// Build context for policy evaluation
|
|
250
|
-
const context = await this.buildUserContext(userId, snapshot);
|
|
251
|
-
if (!context)
|
|
252
|
-
return;
|
|
253
|
-
// Evaluate policies
|
|
254
|
-
const decision = this.policyService.evaluate(context);
|
|
255
|
-
if (decision.shouldScale && decision.action) {
|
|
256
|
-
await this.requestScaling(userId, decision);
|
|
257
|
-
}
|
|
258
|
-
this.emit('evaluation_complete', { userId, decision });
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Build user context for policy evaluation
|
|
262
|
-
*/
|
|
263
|
-
async buildUserContext(userId, snapshot) {
|
|
264
|
-
if (!this.redis)
|
|
265
|
-
return null;
|
|
266
|
-
// Get user plan from Redis cache or database
|
|
267
|
-
const userPlanKey = `${this.config.redisKeyPrefix}user:${userId}:plan`;
|
|
268
|
-
let plan = (await this.redis.get(userPlanKey));
|
|
269
|
-
if (!plan) {
|
|
270
|
-
plan = 'free'; // Default, should be fetched from database
|
|
271
|
-
}
|
|
272
|
-
const maxWorkspaces = this.policyService.getMaxWorkspaces(plan);
|
|
273
|
-
const lastScalingAction = this.lastScalingActions.get(userId);
|
|
274
|
-
return {
|
|
275
|
-
userId,
|
|
276
|
-
plan,
|
|
277
|
-
currentWorkspaceCount: snapshot.workspaces.length,
|
|
278
|
-
maxWorkspaces,
|
|
279
|
-
workspaceMetrics: snapshot.workspaces,
|
|
280
|
-
lastScalingAction,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Request a scaling operation
|
|
285
|
-
*/
|
|
286
|
-
async requestScaling(userId, decision) {
|
|
287
|
-
if (!this.redis || !decision.action)
|
|
288
|
-
return;
|
|
289
|
-
const operation = {
|
|
290
|
-
id: `scale-${userId}-${Date.now()}`,
|
|
291
|
-
userId,
|
|
292
|
-
action: decision.action.type,
|
|
293
|
-
targetWorkspaceId: decision.action.targetWorkspaceId,
|
|
294
|
-
targetResourceTier: decision.action.resourceTier,
|
|
295
|
-
targetAgentLimit: decision.action.newAgentLimit,
|
|
296
|
-
status: 'pending',
|
|
297
|
-
startedAt: new Date(),
|
|
298
|
-
triggeredBy: decision.triggeredPolicy || 'manual',
|
|
299
|
-
metrics: decision.metrics,
|
|
300
|
-
};
|
|
301
|
-
// Acquire distributed lock for this user's scaling
|
|
302
|
-
const lockKey = `${this.config.redisKeyPrefix}scaling:${userId}`;
|
|
303
|
-
const lockAcquired = await this.redis.set(lockKey, operation.id, {
|
|
304
|
-
PX: 60000,
|
|
305
|
-
NX: true,
|
|
306
|
-
});
|
|
307
|
-
if (lockAcquired !== 'OK') {
|
|
308
|
-
// Another scaling operation is in progress
|
|
309
|
-
this.emit('scaling_skipped', { reason: 'lock_held', userId });
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
try {
|
|
313
|
-
// Publish scaling request
|
|
314
|
-
await this.redis.publish(CHANNELS.SCALING_REQUEST, JSON.stringify(operation));
|
|
315
|
-
// Execute the scaling operation
|
|
316
|
-
operation.status = 'in_progress';
|
|
317
|
-
await this.executeScaling(operation, decision);
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
operation.status = 'failed';
|
|
321
|
-
operation.error = error instanceof Error ? error.message : 'Unknown error';
|
|
322
|
-
this.emit('scaling_error', { operation, error });
|
|
323
|
-
}
|
|
324
|
-
finally {
|
|
325
|
-
// Release lock
|
|
326
|
-
await this.redis.del(lockKey);
|
|
327
|
-
// Publish completion
|
|
328
|
-
operation.completedAt = new Date();
|
|
329
|
-
await this.redis.publish(CHANNELS.SCALING_COMPLETE, JSON.stringify(operation));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Execute the actual scaling operation
|
|
334
|
-
*/
|
|
335
|
-
async executeScaling(operation, decision) {
|
|
336
|
-
// This will be integrated with the workspace provisioner
|
|
337
|
-
// For now, emit event for external handling
|
|
338
|
-
this.emit('execute_scaling', { operation, decision });
|
|
339
|
-
// The actual implementation would:
|
|
340
|
-
// 1. Call workspaceProvisioner.provisionWorkspace() for scale_up
|
|
341
|
-
// 2. Call workspaceProvisioner.terminateWorkspace() for scale_down
|
|
342
|
-
// 3. Call coordinator.rebalanceAgents() for rebalance
|
|
343
|
-
operation.status = 'completed';
|
|
344
|
-
this.emit('scaling_executed', operation);
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Report metrics from monitoring service
|
|
348
|
-
*/
|
|
349
|
-
async reportMetrics(userId, workspaces) {
|
|
350
|
-
if (!this.redis)
|
|
351
|
-
return;
|
|
352
|
-
const snapshot = {
|
|
353
|
-
userId,
|
|
354
|
-
workspaces,
|
|
355
|
-
timestamp: new Date(),
|
|
356
|
-
};
|
|
357
|
-
// Cache locally
|
|
358
|
-
this.metricsCache.set(userId, snapshot);
|
|
359
|
-
// Publish to all servers
|
|
360
|
-
await this.redis.publish(CHANNELS.METRICS_UPDATE, JSON.stringify(snapshot));
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Manually trigger scaling evaluation for a user
|
|
364
|
-
*/
|
|
365
|
-
async triggerEvaluation(userId) {
|
|
366
|
-
const snapshot = this.metricsCache.get(userId);
|
|
367
|
-
if (!snapshot)
|
|
368
|
-
return null;
|
|
369
|
-
const context = await this.buildUserContext(userId, snapshot);
|
|
370
|
-
if (!context)
|
|
371
|
-
return null;
|
|
372
|
-
return this.policyService.evaluate(context);
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Get current scaling status
|
|
376
|
-
*/
|
|
377
|
-
getStatus() {
|
|
378
|
-
return {
|
|
379
|
-
enabled: this.config.enabled,
|
|
380
|
-
isLeader: this.isLeader,
|
|
381
|
-
serverId: this.serverId,
|
|
382
|
-
pendingOperations: this.pendingOperations.size,
|
|
383
|
-
cachedUsers: this.metricsCache.size,
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Get pending operations for a user
|
|
388
|
-
*/
|
|
389
|
-
getPendingOperations(userId) {
|
|
390
|
-
const ops = Array.from(this.pendingOperations.values());
|
|
391
|
-
return userId ? ops.filter((op) => op.userId === userId) : ops;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Update user plan in cache
|
|
395
|
-
*/
|
|
396
|
-
async setUserPlan(userId, plan) {
|
|
397
|
-
if (!this.redis)
|
|
398
|
-
return;
|
|
399
|
-
const key = `${this.config.redisKeyPrefix}user:${userId}:plan`;
|
|
400
|
-
await this.redis.set(key, plan, { EX: 3600 }); // 1 hour TTL
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Clean shutdown
|
|
404
|
-
*/
|
|
405
|
-
async shutdown() {
|
|
406
|
-
if (this.evaluationTimer) {
|
|
407
|
-
clearInterval(this.evaluationTimer);
|
|
408
|
-
this.evaluationTimer = null;
|
|
409
|
-
}
|
|
410
|
-
if (this.subscriber) {
|
|
411
|
-
await this.subscriber.unsubscribe();
|
|
412
|
-
await this.subscriber.quit();
|
|
413
|
-
this.subscriber = null;
|
|
414
|
-
}
|
|
415
|
-
if (this.redis) {
|
|
416
|
-
// Release leadership if we have it
|
|
417
|
-
if (this.isLeader) {
|
|
418
|
-
const lockKey = `${this.config.redisKeyPrefix}leader`;
|
|
419
|
-
await this.redis.del(lockKey);
|
|
420
|
-
}
|
|
421
|
-
await this.redis.quit();
|
|
422
|
-
this.redis = null;
|
|
423
|
-
}
|
|
424
|
-
this.emit('shutdown');
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
// Singleton instance
|
|
428
|
-
let _autoScaler = null;
|
|
429
|
-
export function getAutoScaler() {
|
|
430
|
-
if (!_autoScaler) {
|
|
431
|
-
_autoScaler = new AutoScaler();
|
|
432
|
-
}
|
|
433
|
-
return _autoScaler;
|
|
434
|
-
}
|
|
435
|
-
export function createAutoScaler(config = {}) {
|
|
436
|
-
_autoScaler = new AutoScaler(config);
|
|
437
|
-
return _autoScaler;
|
|
438
|
-
}
|
|
439
|
-
//# sourceMappingURL=auto-scaler.js.map
|