@vibescope/mcp-server 0.3.0 → 0.3.3
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-client/blockers.d.ts +46 -0
- package/dist/api-client/blockers.js +43 -0
- package/dist/api-client/cost.d.ts +112 -0
- package/dist/api-client/cost.js +76 -0
- package/dist/api-client/decisions.d.ts +55 -0
- package/dist/api-client/decisions.js +32 -0
- package/dist/api-client/discovery.d.ts +62 -0
- package/dist/api-client/discovery.js +21 -0
- package/dist/api-client/ideas.d.ts +75 -0
- package/dist/api-client/ideas.js +36 -0
- package/dist/api-client/index.d.ts +749 -0
- package/dist/api-client/index.js +291 -0
- package/dist/api-client/project.d.ts +132 -0
- package/dist/api-client/project.js +45 -0
- package/dist/api-client/session.d.ts +163 -0
- package/dist/api-client/session.js +52 -0
- package/dist/api-client/tasks.d.ts +328 -0
- package/dist/api-client/tasks.js +132 -0
- package/dist/api-client/types.d.ts +25 -0
- package/dist/api-client/types.js +4 -0
- package/dist/api-client/worktrees.d.ts +33 -0
- package/dist/api-client/worktrees.js +26 -0
- package/dist/api-client.d.ts +9 -0
- package/dist/api-client.js +104 -25
- package/dist/cli-init.d.ts +17 -0
- package/dist/cli-init.js +445 -0
- package/dist/cli.js +0 -0
- package/dist/handlers/cloud-agents.d.ts +21 -0
- package/dist/handlers/cloud-agents.js +91 -0
- package/dist/handlers/discovery.js +7 -0
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/session.js +3 -1
- package/dist/handlers/tasks.js +10 -12
- package/dist/handlers/types.d.ts +2 -1
- package/dist/handlers/validation.js +5 -1
- package/dist/index.js +8 -3
- package/dist/token-tracking.js +2 -2
- package/dist/tools/blockers.d.ts +13 -0
- package/dist/tools/blockers.js +119 -0
- package/dist/tools/bodies-of-work.d.ts +19 -0
- package/dist/tools/bodies-of-work.js +280 -0
- package/dist/tools/cloud-agents.d.ts +9 -0
- package/dist/tools/cloud-agents.js +67 -0
- package/dist/tools/connectors.d.ts +14 -0
- package/dist/tools/connectors.js +188 -0
- package/dist/tools/cost.d.ts +11 -0
- package/dist/tools/cost.js +108 -0
- package/dist/tools/decisions.d.ts +12 -0
- package/dist/tools/decisions.js +108 -0
- package/dist/tools/deployment.d.ts +24 -0
- package/dist/tools/deployment.js +439 -0
- package/dist/tools/discovery.d.ts +10 -0
- package/dist/tools/discovery.js +73 -0
- package/dist/tools/fallback.d.ts +11 -0
- package/dist/tools/fallback.js +108 -0
- package/dist/tools/file-checkouts.d.ts +13 -0
- package/dist/tools/file-checkouts.js +141 -0
- package/dist/tools/findings.d.ts +13 -0
- package/dist/tools/findings.js +98 -0
- package/dist/tools/git-issues.d.ts +11 -0
- package/dist/tools/git-issues.js +127 -0
- package/dist/tools/ideas.d.ts +13 -0
- package/dist/tools/ideas.js +159 -0
- package/dist/tools/index.d.ts +71 -0
- package/dist/tools/index.js +98 -0
- package/dist/tools/milestones.d.ts +12 -0
- package/dist/tools/milestones.js +115 -0
- package/dist/tools/organizations.d.ts +17 -0
- package/dist/tools/organizations.js +221 -0
- package/dist/tools/progress.d.ts +9 -0
- package/dist/tools/progress.js +70 -0
- package/dist/tools/project.d.ts +13 -0
- package/dist/tools/project.js +199 -0
- package/dist/tools/requests.d.ts +10 -0
- package/dist/tools/requests.js +65 -0
- package/dist/tools/roles.d.ts +11 -0
- package/dist/tools/roles.js +109 -0
- package/dist/tools/session.d.ts +15 -0
- package/dist/tools/session.js +178 -0
- package/dist/tools/sprints.d.ts +18 -0
- package/dist/tools/sprints.js +295 -0
- package/dist/tools/tasks.d.ts +27 -0
- package/dist/tools/tasks.js +539 -0
- package/dist/tools/types.d.ts +7 -0
- package/dist/tools/types.js +6 -0
- package/dist/tools/validation.d.ts +10 -0
- package/dist/tools/validation.js +72 -0
- package/dist/tools/worktrees.d.ts +9 -0
- package/dist/tools/worktrees.js +63 -0
- package/dist/utils.d.ts +66 -0
- package/dist/utils.js +102 -0
- package/docs/TOOLS.md +55 -2
- package/package.json +5 -3
- package/scripts/generate-docs.ts +1 -1
- package/src/api-client/blockers.ts +86 -0
- package/src/api-client/cost.ts +185 -0
- package/src/api-client/decisions.ts +87 -0
- package/src/api-client/discovery.ts +81 -0
- package/src/api-client/ideas.ts +112 -0
- package/src/api-client/index.ts +378 -0
- package/src/api-client/project.ts +179 -0
- package/src/api-client/session.ts +220 -0
- package/src/api-client/tasks.ts +450 -0
- package/src/api-client/types.ts +32 -0
- package/src/api-client/worktrees.ts +53 -0
- package/src/api-client.test.ts +136 -9
- package/src/api-client.ts +125 -27
- package/src/cli-init.ts +504 -0
- package/src/handlers/__test-utils__.ts +2 -0
- package/src/handlers/cloud-agents.ts +138 -0
- package/src/handlers/discovery.ts +7 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/session.ts +3 -1
- package/src/handlers/tasks.ts +10 -12
- package/src/handlers/tool-categories.test.ts +1 -1
- package/src/handlers/types.ts +2 -1
- package/src/handlers/validation.ts +6 -1
- package/src/index.test.ts +2 -2
- package/src/index.ts +8 -2
- package/src/token-tracking.ts +3 -2
- package/src/tools/blockers.ts +122 -0
- package/src/tools/bodies-of-work.ts +283 -0
- package/src/tools/cloud-agents.ts +70 -0
- package/src/tools/connectors.ts +191 -0
- package/src/tools/cost.ts +111 -0
- package/src/tools/decisions.ts +111 -0
- package/src/tools/deployment.ts +442 -0
- package/src/tools/discovery.ts +76 -0
- package/src/tools/fallback.ts +111 -0
- package/src/tools/file-checkouts.ts +145 -0
- package/src/tools/findings.ts +101 -0
- package/src/tools/git-issues.ts +130 -0
- package/src/tools/ideas.ts +162 -0
- package/src/tools/index.ts +131 -0
- package/src/tools/milestones.ts +118 -0
- package/src/tools/organizations.ts +224 -0
- package/src/tools/progress.ts +73 -0
- package/src/tools/project.ts +202 -0
- package/src/tools/requests.ts +68 -0
- package/src/tools/roles.ts +112 -0
- package/src/tools/session.ts +181 -0
- package/src/tools/sprints.ts +298 -0
- package/src/tools/tasks.ts +542 -0
- package/src/tools/tools.test.ts +222 -0
- package/src/tools/types.ts +9 -0
- package/src/tools/validation.ts +75 -0
- package/src/tools/worktrees.ts +66 -0
- package/src/tools.test.ts +1 -1
- package/src/utils.test.ts +229 -0
- package/src/utils.ts +117 -0
- package/dist/tools.d.ts +0 -2
- package/dist/tools.js +0 -3602
- package/src/tools.ts +0 -3607
package/src/api-client.test.ts
CHANGED
|
@@ -23,11 +23,14 @@ const mockFetch = vi.fn();
|
|
|
23
23
|
global.fetch = mockFetch;
|
|
24
24
|
|
|
25
25
|
// Helper to create mock response
|
|
26
|
-
function createMockResponse(data: unknown, ok = true, status = 200) {
|
|
26
|
+
function createMockResponse(data: unknown, ok = true, status = 200, headers?: Record<string, string>) {
|
|
27
27
|
return {
|
|
28
28
|
ok,
|
|
29
29
|
status,
|
|
30
30
|
json: vi.fn().mockResolvedValue(data),
|
|
31
|
+
headers: {
|
|
32
|
+
get: (name: string) => headers?.[name] ?? null,
|
|
33
|
+
},
|
|
31
34
|
};
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -36,12 +39,21 @@ function createNetworkError(message: string) {
|
|
|
36
39
|
return new Error(message);
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
// Fast retry config for tests (1ms delays instead of 1000ms)
|
|
43
|
+
const FAST_RETRY_CONFIG = {
|
|
44
|
+
maxRetries: 3,
|
|
45
|
+
baseDelayMs: 1,
|
|
46
|
+
maxDelayMs: 10,
|
|
47
|
+
retryStatusCodes: [429, 503, 504],
|
|
48
|
+
};
|
|
49
|
+
|
|
39
50
|
describe('VibescopeApiClient', () => {
|
|
40
51
|
let client: VibescopeApiClient;
|
|
41
52
|
|
|
42
53
|
beforeEach(() => {
|
|
43
54
|
vi.clearAllMocks();
|
|
44
|
-
|
|
55
|
+
// Use fast retry config so tests don't take forever
|
|
56
|
+
client = new VibescopeApiClient({ apiKey: 'test-api-key', retry: FAST_RETRY_CONFIG });
|
|
45
57
|
});
|
|
46
58
|
|
|
47
59
|
afterEach(() => {
|
|
@@ -181,29 +193,33 @@ describe('VibescopeApiClient', () => {
|
|
|
181
193
|
});
|
|
182
194
|
});
|
|
183
195
|
|
|
184
|
-
it('should handle network errors', async () => {
|
|
196
|
+
it('should handle network errors after retries', async () => {
|
|
185
197
|
mockFetch.mockRejectedValue(createNetworkError('Network request failed'));
|
|
186
198
|
|
|
187
199
|
const result = await client.validateAuth();
|
|
188
200
|
|
|
201
|
+
// Network errors are retried, so 4 attempts total (1 initial + 3 retries)
|
|
202
|
+
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
189
203
|
expect(result).toEqual({
|
|
190
204
|
ok: false,
|
|
191
205
|
status: 0,
|
|
192
206
|
error: 'Network request failed',
|
|
193
207
|
});
|
|
194
|
-
});
|
|
208
|
+
}, 30000);
|
|
195
209
|
|
|
196
|
-
it('should handle non-Error exceptions', async () => {
|
|
210
|
+
it('should handle non-Error exceptions after retries', async () => {
|
|
197
211
|
mockFetch.mockRejectedValue('String error');
|
|
198
212
|
|
|
199
213
|
const result = await client.validateAuth();
|
|
200
214
|
|
|
215
|
+
// Non-Error exceptions are converted to Error('Network error') and retried
|
|
216
|
+
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
201
217
|
expect(result).toEqual({
|
|
202
218
|
ok: false,
|
|
203
219
|
status: 0,
|
|
204
220
|
error: 'Network error',
|
|
205
221
|
});
|
|
206
|
-
});
|
|
222
|
+
}, 30000);
|
|
207
223
|
});
|
|
208
224
|
|
|
209
225
|
// ============================================================================
|
|
@@ -693,31 +709,142 @@ describe('VibescopeApiClient', () => {
|
|
|
693
709
|
// ============================================================================
|
|
694
710
|
|
|
695
711
|
describe('error handling edge cases', () => {
|
|
696
|
-
it('should handle JSON parse error in response', async () => {
|
|
712
|
+
it('should handle JSON parse error in response after retries', async () => {
|
|
697
713
|
mockFetch.mockResolvedValue({
|
|
698
714
|
ok: true,
|
|
699
715
|
status: 200,
|
|
700
716
|
json: vi.fn().mockRejectedValue(new SyntaxError('Invalid JSON')),
|
|
717
|
+
headers: { get: () => null },
|
|
701
718
|
});
|
|
702
719
|
|
|
703
720
|
const result = await client.validateAuth();
|
|
704
721
|
|
|
722
|
+
// JSON parse errors are treated as network errors and retried
|
|
723
|
+
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
705
724
|
expect(result.ok).toBe(false);
|
|
706
725
|
expect(result.error).toContain('Invalid JSON');
|
|
707
|
-
});
|
|
726
|
+
}, 30000);
|
|
708
727
|
|
|
709
|
-
it('should handle timeout errors', async () => {
|
|
728
|
+
it('should handle timeout errors after retries', async () => {
|
|
710
729
|
const timeoutError = new Error('Request timeout');
|
|
711
730
|
timeoutError.name = 'AbortError';
|
|
712
731
|
mockFetch.mockRejectedValue(timeoutError);
|
|
713
732
|
|
|
714
733
|
const result = await client.validateAuth();
|
|
715
734
|
|
|
735
|
+
// Timeout errors are retried
|
|
736
|
+
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
716
737
|
expect(result).toEqual({
|
|
717
738
|
ok: false,
|
|
718
739
|
status: 0,
|
|
719
740
|
error: 'Request timeout',
|
|
720
741
|
});
|
|
742
|
+
}, 30000);
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// ============================================================================
|
|
746
|
+
// Retry Logic Tests
|
|
747
|
+
// ============================================================================
|
|
748
|
+
|
|
749
|
+
describe('retry logic', () => {
|
|
750
|
+
it('should retry on 429 status code', async () => {
|
|
751
|
+
// First two calls return 429, third succeeds
|
|
752
|
+
mockFetch
|
|
753
|
+
.mockResolvedValueOnce(createMockResponse({ error: 'Rate limited' }, false, 429))
|
|
754
|
+
.mockResolvedValueOnce(createMockResponse({ error: 'Rate limited' }, false, 429))
|
|
755
|
+
.mockResolvedValueOnce(createMockResponse({ valid: true }, true, 200));
|
|
756
|
+
|
|
757
|
+
const result = await client.validateAuth();
|
|
758
|
+
|
|
759
|
+
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
760
|
+
expect(result.ok).toBe(true);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('should retry on 503 status code', async () => {
|
|
764
|
+
mockFetch
|
|
765
|
+
.mockResolvedValueOnce(createMockResponse({ error: 'Service unavailable' }, false, 503))
|
|
766
|
+
.mockResolvedValueOnce(createMockResponse({ valid: true }, true, 200));
|
|
767
|
+
|
|
768
|
+
const result = await client.validateAuth();
|
|
769
|
+
|
|
770
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
771
|
+
expect(result.ok).toBe(true);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('should retry on 504 status code', async () => {
|
|
775
|
+
mockFetch
|
|
776
|
+
.mockResolvedValueOnce(createMockResponse({ error: 'Gateway timeout' }, false, 504))
|
|
777
|
+
.mockResolvedValueOnce(createMockResponse({ valid: true }, true, 200));
|
|
778
|
+
|
|
779
|
+
const result = await client.validateAuth();
|
|
780
|
+
|
|
781
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
782
|
+
expect(result.ok).toBe(true);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('should not retry on non-retryable status codes (400)', async () => {
|
|
786
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ error: 'Bad request' }, false, 400));
|
|
787
|
+
|
|
788
|
+
const result = await client.validateAuth();
|
|
789
|
+
|
|
790
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
791
|
+
expect(result.ok).toBe(false);
|
|
792
|
+
expect(result.status).toBe(400);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('should not retry on non-retryable status codes (401)', async () => {
|
|
796
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ error: 'Unauthorized' }, false, 401));
|
|
797
|
+
|
|
798
|
+
const result = await client.validateAuth();
|
|
799
|
+
|
|
800
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
801
|
+
expect(result.ok).toBe(false);
|
|
802
|
+
expect(result.status).toBe(401);
|
|
721
803
|
});
|
|
804
|
+
|
|
805
|
+
it('should not retry on non-retryable status codes (500)', async () => {
|
|
806
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ error: 'Server error' }, false, 500));
|
|
807
|
+
|
|
808
|
+
const result = await client.validateAuth();
|
|
809
|
+
|
|
810
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
811
|
+
expect(result.ok).toBe(false);
|
|
812
|
+
expect(result.status).toBe(500);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('should stop after max retries (3)', async () => {
|
|
816
|
+
// All 4 attempts (1 initial + 3 retries) return 429
|
|
817
|
+
mockFetch.mockResolvedValue(createMockResponse({ error: 'Rate limited' }, false, 429));
|
|
818
|
+
|
|
819
|
+
const result = await client.validateAuth();
|
|
820
|
+
|
|
821
|
+
expect(mockFetch).toHaveBeenCalledTimes(4); // 1 initial + 3 retries
|
|
822
|
+
expect(result.ok).toBe(false);
|
|
823
|
+
expect(result.status).toBe(429);
|
|
824
|
+
// API error message takes precedence over generic retry message
|
|
825
|
+
expect(result.error).toBe('Rate limited');
|
|
826
|
+
}, 30000);
|
|
827
|
+
|
|
828
|
+
it('should retry on network errors', async () => {
|
|
829
|
+
mockFetch
|
|
830
|
+
.mockRejectedValueOnce(new Error('Network error'))
|
|
831
|
+
.mockResolvedValueOnce(createMockResponse({ valid: true }, true, 200));
|
|
832
|
+
|
|
833
|
+
const result = await client.validateAuth();
|
|
834
|
+
|
|
835
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
836
|
+
expect(result.ok).toBe(true);
|
|
837
|
+
}, 10000);
|
|
838
|
+
|
|
839
|
+
it('should return last error after all retries exhausted on network failure', async () => {
|
|
840
|
+
mockFetch.mockRejectedValue(new Error('Connection refused'));
|
|
841
|
+
|
|
842
|
+
const result = await client.validateAuth();
|
|
843
|
+
|
|
844
|
+
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
845
|
+
expect(result.ok).toBe(false);
|
|
846
|
+
expect(result.status).toBe(0);
|
|
847
|
+
expect(result.error).toBe('Connection refused');
|
|
848
|
+
}, 30000);
|
|
722
849
|
});
|
|
723
850
|
});
|
package/src/api-client.ts
CHANGED
|
@@ -7,9 +7,23 @@
|
|
|
7
7
|
|
|
8
8
|
const DEFAULT_API_URL = 'https://vibescope.dev';
|
|
9
9
|
|
|
10
|
+
// Retry configuration defaults
|
|
11
|
+
const DEFAULT_RETRY_STATUS_CODES = [429, 503, 504];
|
|
12
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
13
|
+
const DEFAULT_BASE_DELAY_MS = 1000;
|
|
14
|
+
const DEFAULT_MAX_DELAY_MS = 30000;
|
|
15
|
+
|
|
16
|
+
interface RetryConfig {
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
baseDelayMs?: number;
|
|
19
|
+
maxDelayMs?: number;
|
|
20
|
+
retryStatusCodes?: number[];
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
interface ApiClientConfig {
|
|
11
24
|
apiKey: string;
|
|
12
25
|
baseUrl?: string;
|
|
26
|
+
retry?: RetryConfig;
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
interface ApiResponse<T> {
|
|
@@ -19,51 +33,134 @@ interface ApiResponse<T> {
|
|
|
19
33
|
error?: string;
|
|
20
34
|
}
|
|
21
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Calculate delay for exponential backoff with jitter
|
|
38
|
+
*/
|
|
39
|
+
function calculateBackoffDelay(
|
|
40
|
+
attempt: number,
|
|
41
|
+
baseDelayMs: number,
|
|
42
|
+
maxDelayMs: number,
|
|
43
|
+
retryAfter?: number
|
|
44
|
+
): number {
|
|
45
|
+
// If Retry-After header is present, use it (in seconds)
|
|
46
|
+
if (retryAfter !== undefined && retryAfter > 0) {
|
|
47
|
+
return Math.min(retryAfter * 1000, maxDelayMs);
|
|
48
|
+
}
|
|
49
|
+
// Exponential backoff: base * 2^attempt with jitter
|
|
50
|
+
const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
|
|
51
|
+
const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter
|
|
52
|
+
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sleep for a specified duration
|
|
57
|
+
*/
|
|
58
|
+
function sleep(ms: number): Promise<void> {
|
|
59
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
60
|
+
}
|
|
61
|
+
|
|
22
62
|
export class VibescopeApiClient {
|
|
23
63
|
private apiKey: string;
|
|
24
64
|
private baseUrl: string;
|
|
65
|
+
private retryConfig: Required<RetryConfig>;
|
|
25
66
|
|
|
26
67
|
constructor(config: ApiClientConfig) {
|
|
27
68
|
this.apiKey = config.apiKey;
|
|
28
69
|
this.baseUrl = config.baseUrl || process.env.VIBESCOPE_API_URL || DEFAULT_API_URL;
|
|
70
|
+
this.retryConfig = {
|
|
71
|
+
maxRetries: config.retry?.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
72
|
+
baseDelayMs: config.retry?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS,
|
|
73
|
+
maxDelayMs: config.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,
|
|
74
|
+
retryStatusCodes: config.retry?.retryStatusCodes ?? DEFAULT_RETRY_STATUS_CODES,
|
|
75
|
+
};
|
|
29
76
|
}
|
|
30
77
|
|
|
31
78
|
private async request<T>(method: string, path: string, body?: unknown): Promise<ApiResponse<T>> {
|
|
32
79
|
const url = `${this.baseUrl}${path}`;
|
|
80
|
+
const { maxRetries, baseDelayMs, maxDelayMs, retryStatusCodes } = this.retryConfig;
|
|
81
|
+
let lastError: Error | null = null;
|
|
82
|
+
let lastResponse: Response | null = null;
|
|
83
|
+
|
|
84
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(url, {
|
|
87
|
+
method,
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'application/json',
|
|
90
|
+
'X-API-Key': this.apiKey
|
|
91
|
+
},
|
|
92
|
+
body: body ? JSON.stringify(body) : undefined
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Check if we should retry this status code
|
|
96
|
+
if (retryStatusCodes.includes(response.status) && attempt < maxRetries) {
|
|
97
|
+
lastResponse = response;
|
|
98
|
+
// Parse Retry-After header if present (can be seconds or HTTP-date)
|
|
99
|
+
const retryAfterHeader = response.headers.get('Retry-After');
|
|
100
|
+
let retryAfter: number | undefined;
|
|
101
|
+
if (retryAfterHeader) {
|
|
102
|
+
const seconds = parseInt(retryAfterHeader, 10);
|
|
103
|
+
if (!isNaN(seconds)) {
|
|
104
|
+
retryAfter = seconds;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const delay = calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs, retryAfter);
|
|
108
|
+
await sleep(delay);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
status: response.status,
|
|
118
|
+
error: data.error || `HTTP ${response.status}`,
|
|
119
|
+
data // Include full response data for additional error context
|
|
120
|
+
};
|
|
121
|
+
}
|
|
33
122
|
|
|
34
|
-
try {
|
|
35
|
-
const response = await fetch(url, {
|
|
36
|
-
method,
|
|
37
|
-
headers: {
|
|
38
|
-
'Content-Type': 'application/json',
|
|
39
|
-
'X-API-Key': this.apiKey
|
|
40
|
-
},
|
|
41
|
-
body: body ? JSON.stringify(body) : undefined
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const data = await response.json();
|
|
45
|
-
|
|
46
|
-
if (!response.ok) {
|
|
47
123
|
return {
|
|
48
|
-
ok:
|
|
124
|
+
ok: true,
|
|
49
125
|
status: response.status,
|
|
50
|
-
|
|
51
|
-
data // Include full response data for additional error context
|
|
126
|
+
data
|
|
52
127
|
};
|
|
128
|
+
} catch (err) {
|
|
129
|
+
lastError = err instanceof Error ? err : new Error('Network error');
|
|
130
|
+
// Retry on network errors (connection failures, timeouts)
|
|
131
|
+
if (attempt < maxRetries) {
|
|
132
|
+
const delay = calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs);
|
|
133
|
+
await sleep(delay);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
53
136
|
}
|
|
137
|
+
}
|
|
54
138
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
139
|
+
// All retries exhausted
|
|
140
|
+
if (lastResponse) {
|
|
141
|
+
// We had a response but it was a retryable error status
|
|
142
|
+
try {
|
|
143
|
+
const data = await lastResponse.json();
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
status: lastResponse.status,
|
|
147
|
+
error: data.error || `HTTP ${lastResponse.status} after ${maxRetries} retries`,
|
|
148
|
+
data
|
|
149
|
+
};
|
|
150
|
+
} catch {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
status: lastResponse.status,
|
|
154
|
+
error: `HTTP ${lastResponse.status} after ${maxRetries} retries`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
66
157
|
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
status: 0,
|
|
162
|
+
error: lastError?.message || 'Network error after retries'
|
|
163
|
+
};
|
|
67
164
|
}
|
|
68
165
|
|
|
69
166
|
// Auth endpoints
|
|
@@ -87,6 +184,7 @@ export class VibescopeApiClient {
|
|
|
87
184
|
role?: string; // Open-ended - any role name accepted
|
|
88
185
|
hostname?: string; // Machine hostname for worktree tracking
|
|
89
186
|
agent_type?: string; // Open-ended - any agent type accepted
|
|
187
|
+
agent_name?: string; // Explicit name for cloud/remote agents (skips persona pool)
|
|
90
188
|
}): Promise<ApiResponse<{
|
|
91
189
|
session_started: boolean;
|
|
92
190
|
session_id?: string;
|