@agent-relay/cloud 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/admin.d.ts +8 -0
- package/dist/api/admin.d.ts.map +1 -0
- package/dist/api/admin.js +225 -0
- package/dist/api/admin.js.map +1 -0
- package/dist/api/auth.d.ts +20 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +136 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/billing.d.ts +7 -0
- package/dist/api/billing.d.ts.map +1 -0
- package/dist/api/billing.js +564 -0
- package/dist/api/billing.js.map +1 -0
- package/dist/api/cli-pty-runner.d.ts +53 -0
- package/dist/api/cli-pty-runner.d.ts.map +1 -0
- package/dist/api/cli-pty-runner.js +193 -0
- package/dist/api/cli-pty-runner.js.map +1 -0
- package/dist/api/codex-auth-helper.d.ts +21 -0
- package/dist/api/codex-auth-helper.d.ts.map +1 -0
- package/dist/api/codex-auth-helper.js +327 -0
- package/dist/api/codex-auth-helper.js.map +1 -0
- package/dist/api/consensus.d.ts +13 -0
- package/dist/api/consensus.d.ts.map +1 -0
- package/dist/api/consensus.js +261 -0
- package/dist/api/consensus.js.map +1 -0
- package/dist/api/coordinators.d.ts +8 -0
- package/dist/api/coordinators.d.ts.map +1 -0
- package/dist/api/coordinators.js +750 -0
- package/dist/api/coordinators.js.map +1 -0
- package/dist/api/daemons.d.ts +12 -0
- package/dist/api/daemons.d.ts.map +1 -0
- package/dist/api/daemons.js +535 -0
- package/dist/api/daemons.js.map +1 -0
- package/dist/api/generic-webhooks.d.ts +8 -0
- package/dist/api/generic-webhooks.d.ts.map +1 -0
- package/dist/api/generic-webhooks.js +129 -0
- package/dist/api/generic-webhooks.js.map +1 -0
- package/dist/api/git.d.ts +8 -0
- package/dist/api/git.d.ts.map +1 -0
- package/dist/api/git.js +269 -0
- package/dist/api/git.js.map +1 -0
- package/dist/api/github-app.d.ts +11 -0
- package/dist/api/github-app.d.ts.map +1 -0
- package/dist/api/github-app.js +223 -0
- package/dist/api/github-app.js.map +1 -0
- package/dist/api/middleware/planLimits.d.ts +43 -0
- package/dist/api/middleware/planLimits.d.ts.map +1 -0
- package/dist/api/middleware/planLimits.js +202 -0
- package/dist/api/middleware/planLimits.js.map +1 -0
- package/dist/api/monitoring.d.ts +11 -0
- package/dist/api/monitoring.d.ts.map +1 -0
- package/dist/api/monitoring.js +578 -0
- package/dist/api/monitoring.js.map +1 -0
- package/dist/api/nango-auth.d.ts +9 -0
- package/dist/api/nango-auth.d.ts.map +1 -0
- package/dist/api/nango-auth.js +674 -0
- package/dist/api/nango-auth.js.map +1 -0
- package/dist/api/onboarding.d.ts +15 -0
- package/dist/api/onboarding.d.ts.map +1 -0
- package/dist/api/onboarding.js +679 -0
- package/dist/api/onboarding.js.map +1 -0
- package/dist/api/policy.d.ts +8 -0
- package/dist/api/policy.d.ts.map +1 -0
- package/dist/api/policy.js +229 -0
- package/dist/api/policy.js.map +1 -0
- package/dist/api/provider-env.d.ts +14 -0
- package/dist/api/provider-env.d.ts.map +1 -0
- package/dist/api/provider-env.js +75 -0
- package/dist/api/provider-env.js.map +1 -0
- package/dist/api/providers.d.ts +7 -0
- package/dist/api/providers.d.ts.map +1 -0
- package/dist/api/providers.js +564 -0
- package/dist/api/providers.js.map +1 -0
- package/dist/api/repos.d.ts +8 -0
- package/dist/api/repos.d.ts.map +1 -0
- package/dist/api/repos.js +577 -0
- package/dist/api/repos.js.map +1 -0
- package/dist/api/sessions.d.ts +11 -0
- package/dist/api/sessions.d.ts.map +1 -0
- package/dist/api/sessions.js +302 -0
- package/dist/api/sessions.js.map +1 -0
- package/dist/api/teams.d.ts +7 -0
- package/dist/api/teams.d.ts.map +1 -0
- package/dist/api/teams.js +281 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/test-helpers.d.ts +10 -0
- package/dist/api/test-helpers.d.ts.map +1 -0
- package/dist/api/test-helpers.js +745 -0
- package/dist/api/test-helpers.js.map +1 -0
- package/dist/api/usage.d.ts +7 -0
- package/dist/api/usage.d.ts.map +1 -0
- package/dist/api/usage.js +111 -0
- package/dist/api/usage.js.map +1 -0
- package/dist/api/webhooks.d.ts +8 -0
- package/dist/api/webhooks.d.ts.map +1 -0
- package/dist/api/webhooks.js +645 -0
- package/dist/api/webhooks.js.map +1 -0
- package/dist/api/workspaces.d.ts +25 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +1799 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/billing/index.d.ts +9 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +9 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/plans.d.ts +39 -0
- package/dist/billing/plans.d.ts.map +1 -0
- package/dist/billing/plans.js +245 -0
- package/dist/billing/plans.js.map +1 -0
- package/dist/billing/service.d.ts +80 -0
- package/dist/billing/service.d.ts.map +1 -0
- package/dist/billing/service.js +388 -0
- package/dist/billing/service.js.map +1 -0
- package/dist/billing/types.d.ts +141 -0
- package/dist/billing/types.d.ts.map +1 -0
- package/dist/billing/types.js +7 -0
- package/dist/billing/types.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/db/bulk-ingest.d.ts +89 -0
- package/dist/db/bulk-ingest.d.ts.map +1 -0
- package/dist/db/bulk-ingest.js +268 -0
- package/dist/db/bulk-ingest.js.map +1 -0
- package/dist/db/drizzle.d.ts +256 -0
- package/dist/db/drizzle.d.ts.map +1 -0
- package/dist/db/drizzle.js +1286 -0
- package/dist/db/drizzle.js.map +1 -0
- package/dist/db/index.d.ts +55 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +68 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +4873 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +620 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/provisioner/index.d.ts +207 -0
- package/dist/provisioner/index.d.ts.map +1 -0
- package/dist/provisioner/index.js +2114 -0
- package/dist/provisioner/index.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1924 -0
- package/dist/server.js.map +1 -0
- package/dist/services/auto-scaler.d.ts +152 -0
- package/dist/services/auto-scaler.d.ts.map +1 -0
- package/dist/services/auto-scaler.js +439 -0
- package/dist/services/auto-scaler.js.map +1 -0
- package/dist/services/capacity-manager.d.ts +148 -0
- package/dist/services/capacity-manager.d.ts.map +1 -0
- package/dist/services/capacity-manager.js +449 -0
- package/dist/services/capacity-manager.js.map +1 -0
- package/dist/services/ci-agent-spawner.d.ts +49 -0
- package/dist/services/ci-agent-spawner.d.ts.map +1 -0
- package/dist/services/ci-agent-spawner.js +373 -0
- package/dist/services/ci-agent-spawner.js.map +1 -0
- package/dist/services/cloud-message-bus.d.ts +28 -0
- package/dist/services/cloud-message-bus.d.ts.map +1 -0
- package/dist/services/cloud-message-bus.js +19 -0
- package/dist/services/cloud-message-bus.js.map +1 -0
- package/dist/services/compute-enforcement.d.ts +57 -0
- package/dist/services/compute-enforcement.d.ts.map +1 -0
- package/dist/services/compute-enforcement.js +175 -0
- package/dist/services/compute-enforcement.js.map +1 -0
- package/dist/services/coordinator.d.ts +62 -0
- package/dist/services/coordinator.d.ts.map +1 -0
- package/dist/services/coordinator.js +389 -0
- package/dist/services/coordinator.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +25 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/intro-expiration.d.ts +60 -0
- package/dist/services/intro-expiration.d.ts.map +1 -0
- package/dist/services/intro-expiration.js +252 -0
- package/dist/services/intro-expiration.js.map +1 -0
- package/dist/services/mention-handler.d.ts +65 -0
- package/dist/services/mention-handler.d.ts.map +1 -0
- package/dist/services/mention-handler.js +405 -0
- package/dist/services/mention-handler.js.map +1 -0
- package/dist/services/nango.d.ts +201 -0
- package/dist/services/nango.d.ts.map +1 -0
- package/dist/services/nango.js +392 -0
- package/dist/services/nango.js.map +1 -0
- package/dist/services/persistence.d.ts +131 -0
- package/dist/services/persistence.d.ts.map +1 -0
- package/dist/services/persistence.js +200 -0
- package/dist/services/persistence.js.map +1 -0
- package/dist/services/planLimits.d.ts +147 -0
- package/dist/services/planLimits.d.ts.map +1 -0
- package/dist/services/planLimits.js +335 -0
- package/dist/services/planLimits.js.map +1 -0
- package/dist/services/presence-registry.d.ts +56 -0
- package/dist/services/presence-registry.d.ts.map +1 -0
- package/dist/services/presence-registry.js +91 -0
- package/dist/services/presence-registry.js.map +1 -0
- package/dist/services/scaling-orchestrator.d.ts +159 -0
- package/dist/services/scaling-orchestrator.d.ts.map +1 -0
- package/dist/services/scaling-orchestrator.js +502 -0
- package/dist/services/scaling-orchestrator.js.map +1 -0
- package/dist/services/scaling-policy.d.ts +121 -0
- package/dist/services/scaling-policy.d.ts.map +1 -0
- package/dist/services/scaling-policy.js +415 -0
- package/dist/services/scaling-policy.js.map +1 -0
- package/dist/services/ssh-security.d.ts +31 -0
- package/dist/services/ssh-security.d.ts.map +1 -0
- package/dist/services/ssh-security.js +63 -0
- package/dist/services/ssh-security.js.map +1 -0
- package/dist/services/workspace-keepalive.d.ts +76 -0
- package/dist/services/workspace-keepalive.d.ts.map +1 -0
- package/dist/services/workspace-keepalive.js +234 -0
- package/dist/services/workspace-keepalive.js.map +1 -0
- package/dist/shims/consensus.d.ts +23 -0
- package/dist/shims/consensus.d.ts.map +1 -0
- package/dist/shims/consensus.js +5 -0
- package/dist/shims/consensus.js.map +1 -0
- package/dist/webhooks/index.d.ts +24 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +29 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/parsers/github.d.ts +8 -0
- package/dist/webhooks/parsers/github.d.ts.map +1 -0
- package/dist/webhooks/parsers/github.js +234 -0
- package/dist/webhooks/parsers/github.js.map +1 -0
- package/dist/webhooks/parsers/index.d.ts +23 -0
- package/dist/webhooks/parsers/index.d.ts.map +1 -0
- package/dist/webhooks/parsers/index.js +30 -0
- package/dist/webhooks/parsers/index.js.map +1 -0
- package/dist/webhooks/parsers/linear.d.ts +9 -0
- package/dist/webhooks/parsers/linear.d.ts.map +1 -0
- package/dist/webhooks/parsers/linear.js +258 -0
- package/dist/webhooks/parsers/linear.js.map +1 -0
- package/dist/webhooks/parsers/slack.d.ts +9 -0
- package/dist/webhooks/parsers/slack.d.ts.map +1 -0
- package/dist/webhooks/parsers/slack.js +214 -0
- package/dist/webhooks/parsers/slack.js.map +1 -0
- package/dist/webhooks/responders/github.d.ts +8 -0
- package/dist/webhooks/responders/github.d.ts.map +1 -0
- package/dist/webhooks/responders/github.js +73 -0
- package/dist/webhooks/responders/github.js.map +1 -0
- package/dist/webhooks/responders/index.d.ts +23 -0
- package/dist/webhooks/responders/index.d.ts.map +1 -0
- package/dist/webhooks/responders/index.js +30 -0
- package/dist/webhooks/responders/index.js.map +1 -0
- package/dist/webhooks/responders/linear.d.ts +9 -0
- package/dist/webhooks/responders/linear.d.ts.map +1 -0
- package/dist/webhooks/responders/linear.js +149 -0
- package/dist/webhooks/responders/linear.js.map +1 -0
- package/dist/webhooks/responders/slack.d.ts +20 -0
- package/dist/webhooks/responders/slack.d.ts.map +1 -0
- package/dist/webhooks/responders/slack.js +178 -0
- package/dist/webhooks/responders/slack.js.map +1 -0
- package/dist/webhooks/router.d.ts +25 -0
- package/dist/webhooks/router.d.ts.map +1 -0
- package/dist/webhooks/router.js +504 -0
- package/dist/webhooks/router.js.map +1 -0
- package/dist/webhooks/rules-engine.d.ts +24 -0
- package/dist/webhooks/rules-engine.d.ts.map +1 -0
- package/dist/webhooks/rules-engine.js +287 -0
- package/dist/webhooks/rules-engine.js.map +1 -0
- package/dist/webhooks/types.d.ts +186 -0
- package/dist/webhooks/types.d.ts.map +1 -0
- package/dist/webhooks/types.js +8 -0
- package/dist/webhooks/types.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Persistence Service
|
|
3
|
+
*
|
|
4
|
+
* Handles durable persistence of agent session data for cloud deployments.
|
|
5
|
+
* Subscribes to wrapper events ('summary', 'session-end') and persists
|
|
6
|
+
* to PostgreSQL via Drizzle ORM.
|
|
7
|
+
*
|
|
8
|
+
* This decouples wrappers from storage concerns - the wrapper emits events,
|
|
9
|
+
* this service handles persistence. Different storage backends can be swapped
|
|
10
|
+
* by implementing alternative persistence services.
|
|
11
|
+
*
|
|
12
|
+
* @see RelayPtyOrchestratorEvents in src/wrapper/relay-pty-orchestrator.ts for event definitions
|
|
13
|
+
*/
|
|
14
|
+
import { eq, and, desc } from 'drizzle-orm';
|
|
15
|
+
import { getDb } from '../db/drizzle.js';
|
|
16
|
+
import { agentSessions, agentSummaries } from '../db/schema.js';
|
|
17
|
+
/**
|
|
18
|
+
* CloudPersistenceService manages durable storage for agent sessions.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const persistence = new CloudPersistenceService({
|
|
23
|
+
* workspaceId: 'workspace-123',
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Bind to a RelayPtyOrchestrator instance
|
|
27
|
+
* const pty = new RelayPtyOrchestrator(config);
|
|
28
|
+
* const sessionId = await persistence.bindToWrapper(pty);
|
|
29
|
+
*
|
|
30
|
+
* // When done, unbind to clean up listeners
|
|
31
|
+
* persistence.unbindFromWrapper(pty);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class CloudPersistenceService {
|
|
35
|
+
config;
|
|
36
|
+
boundWrappers = new Map();
|
|
37
|
+
constructor(config) {
|
|
38
|
+
this.config = config;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Bind to a RelayPtyOrchestrator instance and start persisting its events.
|
|
42
|
+
* Creates a new agent session record and returns the session ID.
|
|
43
|
+
*
|
|
44
|
+
* @param wrapper The RelayPtyOrchestrator to bind to
|
|
45
|
+
* @returns The session ID for this agent session
|
|
46
|
+
*/
|
|
47
|
+
async bindToRelayPtyOrchestrator(wrapper) {
|
|
48
|
+
const db = getDb();
|
|
49
|
+
const agentName = wrapper.name;
|
|
50
|
+
// Create session record
|
|
51
|
+
const result = await db.insert(agentSessions).values({
|
|
52
|
+
workspaceId: this.config.workspaceId,
|
|
53
|
+
agentName,
|
|
54
|
+
status: 'active',
|
|
55
|
+
startedAt: new Date(),
|
|
56
|
+
}).returning();
|
|
57
|
+
const session = result[0];
|
|
58
|
+
if (!session) {
|
|
59
|
+
throw new Error(`Failed to create session for agent ${agentName}`);
|
|
60
|
+
}
|
|
61
|
+
const sessionId = session.id;
|
|
62
|
+
// Create event handlers
|
|
63
|
+
const summaryHandler = async (event) => {
|
|
64
|
+
await this.handleSummary(sessionId, event);
|
|
65
|
+
};
|
|
66
|
+
const sessionEndHandler = async (event) => {
|
|
67
|
+
await this.handleSessionEnd(sessionId, event);
|
|
68
|
+
};
|
|
69
|
+
// Bind handlers
|
|
70
|
+
wrapper.on('summary', summaryHandler);
|
|
71
|
+
wrapper.on('session-end', sessionEndHandler);
|
|
72
|
+
// Track binding for cleanup
|
|
73
|
+
this.boundWrappers.set(wrapper, {
|
|
74
|
+
sessionId,
|
|
75
|
+
summaryHandler,
|
|
76
|
+
sessionEndHandler,
|
|
77
|
+
});
|
|
78
|
+
console.log(`[persistence] Bound to ${agentName}, session=${sessionId}`);
|
|
79
|
+
return sessionId;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Unbind from a RelayPtyOrchestrator and clean up event listeners.
|
|
83
|
+
*
|
|
84
|
+
* @param wrapper The RelayPtyOrchestrator to unbind from
|
|
85
|
+
*/
|
|
86
|
+
unbindFromRelayPtyOrchestrator(wrapper) {
|
|
87
|
+
const binding = this.boundWrappers.get(wrapper);
|
|
88
|
+
if (!binding)
|
|
89
|
+
return;
|
|
90
|
+
wrapper.off('summary', binding.summaryHandler);
|
|
91
|
+
wrapper.off('session-end', binding.sessionEndHandler);
|
|
92
|
+
this.boundWrappers.delete(wrapper);
|
|
93
|
+
console.log(`[persistence] Unbound from ${wrapper.name}`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Handle a summary event - persist to agent_summaries table.
|
|
97
|
+
*/
|
|
98
|
+
async handleSummary(sessionId, event) {
|
|
99
|
+
try {
|
|
100
|
+
const db = getDb();
|
|
101
|
+
const result = await db.insert(agentSummaries).values({
|
|
102
|
+
sessionId,
|
|
103
|
+
agentName: event.agentName,
|
|
104
|
+
summary: event.summary,
|
|
105
|
+
createdAt: new Date(),
|
|
106
|
+
}).returning();
|
|
107
|
+
const summaryRecord = result[0];
|
|
108
|
+
if (!summaryRecord) {
|
|
109
|
+
console.error(`[persistence] Insert returned no record for ${event.agentName}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(`[persistence] Saved summary for ${event.agentName}: ${event.summary.currentTask || 'no task'}`);
|
|
113
|
+
this.config.onSummaryPersisted?.(event.agentName, summaryRecord.id);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error(`[persistence] Failed to save summary for ${event.agentName}:`, err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Handle a session-end event - update agent_sessions with end marker.
|
|
121
|
+
*/
|
|
122
|
+
async handleSessionEnd(sessionId, event) {
|
|
123
|
+
try {
|
|
124
|
+
const db = getDb();
|
|
125
|
+
await db.update(agentSessions)
|
|
126
|
+
.set({
|
|
127
|
+
status: 'ended',
|
|
128
|
+
endedAt: new Date(),
|
|
129
|
+
endMarker: event.marker,
|
|
130
|
+
})
|
|
131
|
+
.where(eq(agentSessions.id, sessionId));
|
|
132
|
+
console.log(`[persistence] Session ended for ${event.agentName}: ${event.marker.summary || 'no summary'}`);
|
|
133
|
+
this.config.onSessionEnded?.(event.agentName, sessionId);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error(`[persistence] Failed to end session for ${event.agentName}:`, err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get the session ID for a bound wrapper.
|
|
141
|
+
*/
|
|
142
|
+
getSessionId(wrapper) {
|
|
143
|
+
return this.boundWrappers.get(wrapper)?.sessionId;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get all summaries for a session.
|
|
147
|
+
*/
|
|
148
|
+
async getSessionSummaries(sessionId) {
|
|
149
|
+
const db = getDb();
|
|
150
|
+
return db.select()
|
|
151
|
+
.from(agentSummaries)
|
|
152
|
+
.where(eq(agentSummaries.sessionId, sessionId))
|
|
153
|
+
.orderBy(agentSummaries.createdAt);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the latest summary for an agent in THIS workspace.
|
|
157
|
+
* Joins through agent_sessions to ensure workspace scoping.
|
|
158
|
+
*/
|
|
159
|
+
async getLatestSummary(agentName) {
|
|
160
|
+
const db = getDb();
|
|
161
|
+
// Join with sessions to ensure we only get summaries from this workspace
|
|
162
|
+
const results = await db.select({
|
|
163
|
+
id: agentSummaries.id,
|
|
164
|
+
sessionId: agentSummaries.sessionId,
|
|
165
|
+
agentName: agentSummaries.agentName,
|
|
166
|
+
summary: agentSummaries.summary,
|
|
167
|
+
createdAt: agentSummaries.createdAt,
|
|
168
|
+
})
|
|
169
|
+
.from(agentSummaries)
|
|
170
|
+
.innerJoin(agentSessions, eq(agentSummaries.sessionId, agentSessions.id))
|
|
171
|
+
.where(and(eq(agentSummaries.agentName, agentName), eq(agentSessions.workspaceId, this.config.workspaceId)))
|
|
172
|
+
.orderBy(desc(agentSummaries.createdAt))
|
|
173
|
+
.limit(1);
|
|
174
|
+
return results[0] || null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get active sessions for a workspace.
|
|
178
|
+
*/
|
|
179
|
+
async getActiveSessions() {
|
|
180
|
+
const db = getDb();
|
|
181
|
+
return db.select()
|
|
182
|
+
.from(agentSessions)
|
|
183
|
+
.where(and(eq(agentSessions.workspaceId, this.config.workspaceId), eq(agentSessions.status, 'active')));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Clean up all bindings.
|
|
187
|
+
*/
|
|
188
|
+
destroy() {
|
|
189
|
+
for (const wrapper of this.boundWrappers.keys()) {
|
|
190
|
+
this.unbindFromRelayPtyOrchestrator(wrapper);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Factory function to create a persistence service for a workspace.
|
|
196
|
+
*/
|
|
197
|
+
export function createPersistenceService(workspaceId) {
|
|
198
|
+
return new CloudPersistenceService({ workspaceId });
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/services/persistence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Limits Service
|
|
3
|
+
*
|
|
4
|
+
* Defines resource limits for each plan tier and provides
|
|
5
|
+
* functions to check if users are within their limits.
|
|
6
|
+
*/
|
|
7
|
+
import { PlanType } from '../db/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resource limits for each plan tier
|
|
10
|
+
*/
|
|
11
|
+
export interface PlanLimits {
|
|
12
|
+
maxWorkspaces: number;
|
|
13
|
+
maxRepos: number;
|
|
14
|
+
maxConcurrentAgents: number;
|
|
15
|
+
maxComputeHoursPerMonth: number;
|
|
16
|
+
coordinatorsEnabled: boolean;
|
|
17
|
+
/** Cloud session persistence (summaries, session tracking) - Pro+ only */
|
|
18
|
+
sessionPersistence: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Plan limits configuration
|
|
22
|
+
*
|
|
23
|
+
* Free: Try it out on a side project
|
|
24
|
+
* Pro: Professional developers, coordinators enabled
|
|
25
|
+
* Team: Growing teams with advanced needs
|
|
26
|
+
* Enterprise: Unlimited everything
|
|
27
|
+
*/
|
|
28
|
+
export declare const PLAN_LIMITS: Record<PlanType, PlanLimits>;
|
|
29
|
+
/**
|
|
30
|
+
* Get plan limits for a given plan type
|
|
31
|
+
*/
|
|
32
|
+
export declare function getPlanLimits(plan: PlanType): PlanLimits;
|
|
33
|
+
/**
|
|
34
|
+
* Current usage for a user
|
|
35
|
+
*/
|
|
36
|
+
export interface UserUsage {
|
|
37
|
+
workspaceCount: number;
|
|
38
|
+
repoCount: number;
|
|
39
|
+
concurrentAgents: number;
|
|
40
|
+
computeHoursThisMonth: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get current usage for a user
|
|
44
|
+
*/
|
|
45
|
+
export declare function getUserUsage(userId: string): Promise<UserUsage>;
|
|
46
|
+
/**
|
|
47
|
+
* Check if user can create a new workspace
|
|
48
|
+
*/
|
|
49
|
+
export declare function canCreateWorkspace(userId: string): Promise<{
|
|
50
|
+
allowed: boolean;
|
|
51
|
+
reason?: string;
|
|
52
|
+
limit?: number;
|
|
53
|
+
current?: number;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if user can add a new repo
|
|
57
|
+
*/
|
|
58
|
+
export declare function canAddRepo(userId: string): Promise<{
|
|
59
|
+
allowed: boolean;
|
|
60
|
+
reason?: string;
|
|
61
|
+
limit?: number;
|
|
62
|
+
current?: number;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Check if user can spawn another agent (concurrent agent limit)
|
|
66
|
+
*/
|
|
67
|
+
export declare function canSpawnAgent(userId: string, currentRunningAgents?: number): Promise<{
|
|
68
|
+
allowed: boolean;
|
|
69
|
+
reason?: string;
|
|
70
|
+
limit?: number;
|
|
71
|
+
current?: number;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Check if user can use coordinator agents
|
|
75
|
+
*/
|
|
76
|
+
export declare function canUseCoordinator(userId: string): Promise<{
|
|
77
|
+
allowed: boolean;
|
|
78
|
+
reason?: string;
|
|
79
|
+
requiredPlan?: string;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Check if user can use cloud session persistence
|
|
83
|
+
*
|
|
84
|
+
* Session persistence enables:
|
|
85
|
+
* - [[SUMMARY]] blocks saved to cloud database
|
|
86
|
+
* - [[SESSION_END]] markers for session tracking
|
|
87
|
+
* - Session recovery and handoff between agents
|
|
88
|
+
*/
|
|
89
|
+
export declare function canUseSessionPersistence(userId: string): Promise<{
|
|
90
|
+
allowed: boolean;
|
|
91
|
+
reason?: string;
|
|
92
|
+
requiredPlan?: string;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Check if user has compute hours available
|
|
96
|
+
*/
|
|
97
|
+
export declare function hasComputeHoursAvailable(userId: string): Promise<{
|
|
98
|
+
available: boolean;
|
|
99
|
+
reason?: string;
|
|
100
|
+
limit?: number;
|
|
101
|
+
current?: number;
|
|
102
|
+
}>;
|
|
103
|
+
/**
|
|
104
|
+
* Get remaining quota for a user
|
|
105
|
+
*/
|
|
106
|
+
export declare function getRemainingQuota(userId: string): Promise<{
|
|
107
|
+
plan: PlanType;
|
|
108
|
+
limits: PlanLimits;
|
|
109
|
+
usage: UserUsage;
|
|
110
|
+
remaining: {
|
|
111
|
+
workspaces: number;
|
|
112
|
+
repos: number;
|
|
113
|
+
concurrentAgents: number;
|
|
114
|
+
computeHours: number;
|
|
115
|
+
};
|
|
116
|
+
}>;
|
|
117
|
+
/**
|
|
118
|
+
* Record compute usage
|
|
119
|
+
*/
|
|
120
|
+
export declare function recordComputeUsage(userId: string, workspaceId: string, hours: number): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Update active agent count for a user
|
|
123
|
+
*/
|
|
124
|
+
export declare function updateActiveAgentCount(userId: string, workspaceId: string, count: number): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Resource tier name type
|
|
127
|
+
*/
|
|
128
|
+
export type ResourceTierName = 'small' | 'medium' | 'large' | 'xlarge';
|
|
129
|
+
/**
|
|
130
|
+
* Get the default resource tier for a plan
|
|
131
|
+
* Maps plans to appropriate compute resources
|
|
132
|
+
*/
|
|
133
|
+
export declare function getResourceTierForPlan(plan: PlanType): ResourceTierName;
|
|
134
|
+
/**
|
|
135
|
+
* Get the maximum resource tier a plan can scale to
|
|
136
|
+
* Prevents over-scaling beyond plan entitlements
|
|
137
|
+
*/
|
|
138
|
+
export declare function getMaxResourceTierForPlan(plan: PlanType): ResourceTierName;
|
|
139
|
+
/**
|
|
140
|
+
* Check if user's plan allows auto-scaling
|
|
141
|
+
*/
|
|
142
|
+
export declare function canAutoScale(plan: PlanType): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Check if auto-scale to a specific tier is allowed for a plan
|
|
145
|
+
*/
|
|
146
|
+
export declare function canScaleToTier(plan: PlanType, targetTier: ResourceTierName): boolean;
|
|
147
|
+
//# sourceMappingURL=planLimits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planLimits.d.ts","sourceRoot":"","sources":["../../src/services/planLimits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Limits Service
|
|
3
|
+
*
|
|
4
|
+
* Defines resource limits for each plan tier and provides
|
|
5
|
+
* functions to check if users are within their limits.
|
|
6
|
+
*/
|
|
7
|
+
import { db, usageRecordsTable } from '../db/index.js';
|
|
8
|
+
import { eq, and, gte, sql } from 'drizzle-orm';
|
|
9
|
+
import { getDb } from '../db/drizzle.js';
|
|
10
|
+
/**
|
|
11
|
+
* Plan limits configuration
|
|
12
|
+
*
|
|
13
|
+
* Free: Try it out on a side project
|
|
14
|
+
* Pro: Professional developers, coordinators enabled
|
|
15
|
+
* Team: Growing teams with advanced needs
|
|
16
|
+
* Enterprise: Unlimited everything
|
|
17
|
+
*/
|
|
18
|
+
export const PLAN_LIMITS = {
|
|
19
|
+
free: {
|
|
20
|
+
maxWorkspaces: 1,
|
|
21
|
+
maxRepos: 2,
|
|
22
|
+
maxConcurrentAgents: 2,
|
|
23
|
+
maxComputeHoursPerMonth: 5, // Limited free tier
|
|
24
|
+
coordinatorsEnabled: false,
|
|
25
|
+
sessionPersistence: false,
|
|
26
|
+
},
|
|
27
|
+
pro: {
|
|
28
|
+
maxWorkspaces: 5,
|
|
29
|
+
maxRepos: 10,
|
|
30
|
+
maxConcurrentAgents: 5,
|
|
31
|
+
maxComputeHoursPerMonth: 50,
|
|
32
|
+
coordinatorsEnabled: true,
|
|
33
|
+
sessionPersistence: true,
|
|
34
|
+
},
|
|
35
|
+
team: {
|
|
36
|
+
maxWorkspaces: 20,
|
|
37
|
+
maxRepos: 100,
|
|
38
|
+
maxConcurrentAgents: 50,
|
|
39
|
+
maxComputeHoursPerMonth: 500,
|
|
40
|
+
coordinatorsEnabled: true,
|
|
41
|
+
sessionPersistence: true,
|
|
42
|
+
},
|
|
43
|
+
enterprise: {
|
|
44
|
+
maxWorkspaces: Infinity,
|
|
45
|
+
maxRepos: Infinity,
|
|
46
|
+
maxConcurrentAgents: Infinity,
|
|
47
|
+
maxComputeHoursPerMonth: Infinity,
|
|
48
|
+
coordinatorsEnabled: true,
|
|
49
|
+
sessionPersistence: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Get plan limits for a given plan type
|
|
54
|
+
*/
|
|
55
|
+
export function getPlanLimits(plan) {
|
|
56
|
+
return PLAN_LIMITS[plan];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get current usage for a user
|
|
60
|
+
*/
|
|
61
|
+
export async function getUserUsage(userId) {
|
|
62
|
+
// Get workspace count
|
|
63
|
+
const workspaces = await db.workspaces.findByUserId(userId);
|
|
64
|
+
const workspaceCount = workspaces.length;
|
|
65
|
+
// Get repo count across all workspaces
|
|
66
|
+
let repoCount = 0;
|
|
67
|
+
for (const workspace of workspaces) {
|
|
68
|
+
const repos = await db.repositories.findByWorkspaceId(workspace.id);
|
|
69
|
+
repoCount += repos.length;
|
|
70
|
+
}
|
|
71
|
+
// Get concurrent agents (currently running)
|
|
72
|
+
// For now, we'll track this via usage_records with metric 'active_agents'
|
|
73
|
+
// In production, this would query the actual running agent count
|
|
74
|
+
const drizzleDb = getDb();
|
|
75
|
+
const activeAgentsResult = await drizzleDb
|
|
76
|
+
.select({ total: sql `COALESCE(MAX(${usageRecordsTable.value}), 0)` })
|
|
77
|
+
.from(usageRecordsTable)
|
|
78
|
+
.where(and(eq(usageRecordsTable.userId, userId), eq(usageRecordsTable.metric, 'active_agents')));
|
|
79
|
+
const concurrentAgents = Number(activeAgentsResult[0]?.total || 0);
|
|
80
|
+
// Get compute hours this month
|
|
81
|
+
const startOfMonth = new Date();
|
|
82
|
+
startOfMonth.setDate(1);
|
|
83
|
+
startOfMonth.setHours(0, 0, 0, 0);
|
|
84
|
+
const computeHoursResult = await drizzleDb
|
|
85
|
+
.select({ total: sql `COALESCE(SUM(${usageRecordsTable.value}), 0)` })
|
|
86
|
+
.from(usageRecordsTable)
|
|
87
|
+
.where(and(eq(usageRecordsTable.userId, userId), eq(usageRecordsTable.metric, 'compute_hours'), gte(usageRecordsTable.recordedAt, startOfMonth)));
|
|
88
|
+
const computeHoursThisMonth = Number(computeHoursResult[0]?.total || 0);
|
|
89
|
+
return {
|
|
90
|
+
workspaceCount,
|
|
91
|
+
repoCount,
|
|
92
|
+
concurrentAgents,
|
|
93
|
+
computeHoursThisMonth,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if user can create a new workspace
|
|
98
|
+
*/
|
|
99
|
+
export async function canCreateWorkspace(userId) {
|
|
100
|
+
const user = await db.users.findById(userId);
|
|
101
|
+
if (!user) {
|
|
102
|
+
return { allowed: false, reason: 'User not found' };
|
|
103
|
+
}
|
|
104
|
+
const plan = user.plan || 'free';
|
|
105
|
+
const limits = getPlanLimits(plan);
|
|
106
|
+
const usage = await getUserUsage(userId);
|
|
107
|
+
if (usage.workspaceCount >= limits.maxWorkspaces) {
|
|
108
|
+
return {
|
|
109
|
+
allowed: false,
|
|
110
|
+
reason: `Workspace limit reached for ${plan} plan`,
|
|
111
|
+
limit: limits.maxWorkspaces,
|
|
112
|
+
current: usage.workspaceCount,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { allowed: true };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if user can add a new repo
|
|
119
|
+
*/
|
|
120
|
+
export async function canAddRepo(userId) {
|
|
121
|
+
const user = await db.users.findById(userId);
|
|
122
|
+
if (!user) {
|
|
123
|
+
return { allowed: false, reason: 'User not found' };
|
|
124
|
+
}
|
|
125
|
+
const plan = user.plan || 'free';
|
|
126
|
+
const limits = getPlanLimits(plan);
|
|
127
|
+
const usage = await getUserUsage(userId);
|
|
128
|
+
if (usage.repoCount >= limits.maxRepos) {
|
|
129
|
+
return {
|
|
130
|
+
allowed: false,
|
|
131
|
+
reason: `Repository limit reached for ${plan} plan`,
|
|
132
|
+
limit: limits.maxRepos,
|
|
133
|
+
current: usage.repoCount,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return { allowed: true };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if user can spawn another agent (concurrent agent limit)
|
|
140
|
+
*/
|
|
141
|
+
export async function canSpawnAgent(userId, currentRunningAgents) {
|
|
142
|
+
const user = await db.users.findById(userId);
|
|
143
|
+
if (!user) {
|
|
144
|
+
return { allowed: false, reason: 'User not found' };
|
|
145
|
+
}
|
|
146
|
+
const plan = user.plan || 'free';
|
|
147
|
+
const limits = getPlanLimits(plan);
|
|
148
|
+
// Use provided count or fetch from usage
|
|
149
|
+
let concurrentAgents;
|
|
150
|
+
if (currentRunningAgents !== undefined) {
|
|
151
|
+
concurrentAgents = currentRunningAgents;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const usage = await getUserUsage(userId);
|
|
155
|
+
concurrentAgents = usage.concurrentAgents;
|
|
156
|
+
}
|
|
157
|
+
if (concurrentAgents >= limits.maxConcurrentAgents) {
|
|
158
|
+
return {
|
|
159
|
+
allowed: false,
|
|
160
|
+
reason: `Concurrent agent limit reached for ${plan} plan`,
|
|
161
|
+
limit: limits.maxConcurrentAgents,
|
|
162
|
+
current: concurrentAgents,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return { allowed: true };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Check if user can use coordinator agents
|
|
169
|
+
*/
|
|
170
|
+
export async function canUseCoordinator(userId) {
|
|
171
|
+
const user = await db.users.findById(userId);
|
|
172
|
+
if (!user) {
|
|
173
|
+
return { allowed: false, reason: 'User not found' };
|
|
174
|
+
}
|
|
175
|
+
const plan = user.plan || 'free';
|
|
176
|
+
const limits = getPlanLimits(plan);
|
|
177
|
+
if (!limits.coordinatorsEnabled) {
|
|
178
|
+
return {
|
|
179
|
+
allowed: false,
|
|
180
|
+
reason: 'Coordinator agents require a Pro plan or higher',
|
|
181
|
+
requiredPlan: 'pro',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { allowed: true };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check if user can use cloud session persistence
|
|
188
|
+
*
|
|
189
|
+
* Session persistence enables:
|
|
190
|
+
* - [[SUMMARY]] blocks saved to cloud database
|
|
191
|
+
* - [[SESSION_END]] markers for session tracking
|
|
192
|
+
* - Session recovery and handoff between agents
|
|
193
|
+
*/
|
|
194
|
+
export async function canUseSessionPersistence(userId) {
|
|
195
|
+
const user = await db.users.findById(userId);
|
|
196
|
+
if (!user) {
|
|
197
|
+
return { allowed: false, reason: 'User not found' };
|
|
198
|
+
}
|
|
199
|
+
const plan = user.plan || 'free';
|
|
200
|
+
const limits = getPlanLimits(plan);
|
|
201
|
+
if (!limits.sessionPersistence) {
|
|
202
|
+
return {
|
|
203
|
+
allowed: false,
|
|
204
|
+
reason: 'Cloud session persistence requires a Pro plan or higher',
|
|
205
|
+
requiredPlan: 'pro',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return { allowed: true };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if user has compute hours available
|
|
212
|
+
*/
|
|
213
|
+
export async function hasComputeHoursAvailable(userId) {
|
|
214
|
+
const user = await db.users.findById(userId);
|
|
215
|
+
if (!user) {
|
|
216
|
+
return { available: false, reason: 'User not found' };
|
|
217
|
+
}
|
|
218
|
+
const plan = user.plan || 'free';
|
|
219
|
+
const limits = getPlanLimits(plan);
|
|
220
|
+
const usage = await getUserUsage(userId);
|
|
221
|
+
// Enterprise has unlimited
|
|
222
|
+
if (limits.maxComputeHoursPerMonth === Infinity) {
|
|
223
|
+
return { available: true };
|
|
224
|
+
}
|
|
225
|
+
if (usage.computeHoursThisMonth >= limits.maxComputeHoursPerMonth) {
|
|
226
|
+
return {
|
|
227
|
+
available: false,
|
|
228
|
+
reason: `Compute hours limit reached for ${plan} plan`,
|
|
229
|
+
limit: limits.maxComputeHoursPerMonth,
|
|
230
|
+
current: usage.computeHoursThisMonth,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return { available: true };
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get remaining quota for a user
|
|
237
|
+
*/
|
|
238
|
+
export async function getRemainingQuota(userId) {
|
|
239
|
+
const user = await db.users.findById(userId);
|
|
240
|
+
const plan = (user?.plan || 'free');
|
|
241
|
+
const limits = getPlanLimits(plan);
|
|
242
|
+
const usage = await getUserUsage(userId);
|
|
243
|
+
const calcRemaining = (limit, current) => limit === Infinity ? Infinity : Math.max(0, limit - current);
|
|
244
|
+
return {
|
|
245
|
+
plan,
|
|
246
|
+
limits,
|
|
247
|
+
usage,
|
|
248
|
+
remaining: {
|
|
249
|
+
workspaces: calcRemaining(limits.maxWorkspaces, usage.workspaceCount),
|
|
250
|
+
repos: calcRemaining(limits.maxRepos, usage.repoCount),
|
|
251
|
+
concurrentAgents: calcRemaining(limits.maxConcurrentAgents, usage.concurrentAgents),
|
|
252
|
+
computeHours: calcRemaining(limits.maxComputeHoursPerMonth, usage.computeHoursThisMonth),
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Record compute usage
|
|
258
|
+
*/
|
|
259
|
+
export async function recordComputeUsage(userId, workspaceId, hours) {
|
|
260
|
+
const drizzleDb = getDb();
|
|
261
|
+
await drizzleDb.insert(usageRecordsTable).values({
|
|
262
|
+
userId,
|
|
263
|
+
workspaceId,
|
|
264
|
+
metric: 'compute_hours',
|
|
265
|
+
value: Math.round(hours * 100) / 100, // Round to 2 decimal places
|
|
266
|
+
recordedAt: new Date(),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Update active agent count for a user
|
|
271
|
+
*/
|
|
272
|
+
export async function updateActiveAgentCount(userId, workspaceId, count) {
|
|
273
|
+
const drizzleDb = getDb();
|
|
274
|
+
await drizzleDb.insert(usageRecordsTable).values({
|
|
275
|
+
userId,
|
|
276
|
+
workspaceId,
|
|
277
|
+
metric: 'active_agents',
|
|
278
|
+
value: count,
|
|
279
|
+
recordedAt: new Date(),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get the default resource tier for a plan
|
|
284
|
+
* Maps plans to appropriate compute resources
|
|
285
|
+
*/
|
|
286
|
+
export function getResourceTierForPlan(plan) {
|
|
287
|
+
switch (plan) {
|
|
288
|
+
case 'free':
|
|
289
|
+
return 'small'; // 2GB, 2 CPUs - suitable for 2 agents
|
|
290
|
+
case 'pro':
|
|
291
|
+
return 'medium'; // 4GB, 4 CPUs - suitable for 5 agents
|
|
292
|
+
case 'team':
|
|
293
|
+
return 'large'; // 8GB, 4 CPUs - suitable for 10 agents
|
|
294
|
+
case 'enterprise':
|
|
295
|
+
return 'xlarge'; // 16GB, 8 CPUs - suitable for 20 agents
|
|
296
|
+
default:
|
|
297
|
+
return 'small';
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get the maximum resource tier a plan can scale to
|
|
302
|
+
* Prevents over-scaling beyond plan entitlements
|
|
303
|
+
*/
|
|
304
|
+
export function getMaxResourceTierForPlan(plan) {
|
|
305
|
+
switch (plan) {
|
|
306
|
+
case 'free':
|
|
307
|
+
return 'small'; // Free tier cannot scale up
|
|
308
|
+
case 'pro':
|
|
309
|
+
return 'medium'; // Pro can scale to medium
|
|
310
|
+
case 'team':
|
|
311
|
+
return 'large'; // Team can scale to large
|
|
312
|
+
case 'enterprise':
|
|
313
|
+
return 'xlarge'; // Enterprise can use any tier
|
|
314
|
+
default:
|
|
315
|
+
return 'small';
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Check if user's plan allows auto-scaling
|
|
320
|
+
*/
|
|
321
|
+
export function canAutoScale(plan) {
|
|
322
|
+
// Only Pro and above can auto-scale
|
|
323
|
+
return plan !== 'free';
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Check if auto-scale to a specific tier is allowed for a plan
|
|
327
|
+
*/
|
|
328
|
+
export function canScaleToTier(plan, targetTier) {
|
|
329
|
+
const tierOrder = ['small', 'medium', 'large', 'xlarge'];
|
|
330
|
+
const maxTier = getMaxResourceTierForPlan(plan);
|
|
331
|
+
const targetIndex = tierOrder.indexOf(targetTier);
|
|
332
|
+
const maxIndex = tierOrder.indexOf(maxTier);
|
|
333
|
+
return targetIndex <= maxIndex;
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=planLimits.js.map
|