gsd-agent 1.0.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/README.md +221 -0
- package/bin/cli.js +313 -0
- package/dist/auth-flow.d.ts +50 -0
- package/dist/auth-flow.d.ts.map +1 -0
- package/dist/auth-flow.js +233 -0
- package/dist/auth-flow.js.map +1 -0
- package/dist/auth.d.ts +42 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +117 -0
- package/dist/auth.js.map +1 -0
- package/dist/command-executor.d.ts +44 -0
- package/dist/command-executor.d.ts.map +1 -0
- package/dist/command-executor.js +193 -0
- package/dist/command-executor.js.map +1 -0
- package/dist/command-executor.test.d.ts +8 -0
- package/dist/command-executor.test.d.ts.map +1 -0
- package/dist/command-executor.test.js +87 -0
- package/dist/command-executor.test.js.map +1 -0
- package/dist/command-queue.d.ts +44 -0
- package/dist/command-queue.d.ts.map +1 -0
- package/dist/command-queue.js +184 -0
- package/dist/command-queue.js.map +1 -0
- package/dist/command-queue.test.d.ts +7 -0
- package/dist/command-queue.test.d.ts.map +1 -0
- package/dist/command-queue.test.js +220 -0
- package/dist/command-queue.test.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +103 -0
- package/dist/config.js.map +1 -0
- package/dist/conflict-resolver.d.ts +43 -0
- package/dist/conflict-resolver.d.ts.map +1 -0
- package/dist/conflict-resolver.js +91 -0
- package/dist/conflict-resolver.js.map +1 -0
- package/dist/conflict-resolver.test.d.ts +7 -0
- package/dist/conflict-resolver.test.d.ts.map +1 -0
- package/dist/conflict-resolver.test.js +123 -0
- package/dist/conflict-resolver.test.js.map +1 -0
- package/dist/discovery.d.ts +59 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +180 -0
- package/dist/discovery.js.map +1 -0
- package/dist/discovery.test.d.ts +8 -0
- package/dist/discovery.test.d.ts.map +1 -0
- package/dist/discovery.test.js +132 -0
- package/dist/discovery.test.js.map +1 -0
- package/dist/hash.d.ts +20 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +35 -0
- package/dist/hash.js.map +1 -0
- package/dist/hash.test.d.ts +7 -0
- package/dist/hash.test.d.ts.map +1 -0
- package/dist/hash.test.js +58 -0
- package/dist/hash.test.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +202 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.test.d.ts +8 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +37 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/logger.d.ts +68 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +159 -0
- package/dist/logger.js.map +1 -0
- package/dist/output-streamer.d.ts +27 -0
- package/dist/output-streamer.d.ts.map +1 -0
- package/dist/output-streamer.js +71 -0
- package/dist/output-streamer.js.map +1 -0
- package/dist/output-streamer.test.d.ts +7 -0
- package/dist/output-streamer.test.d.ts.map +1 -0
- package/dist/output-streamer.test.js +90 -0
- package/dist/output-streamer.test.js.map +1 -0
- package/dist/realtime-subscriber.d.ts +63 -0
- package/dist/realtime-subscriber.d.ts.map +1 -0
- package/dist/realtime-subscriber.js +201 -0
- package/dist/realtime-subscriber.js.map +1 -0
- package/dist/realtime-subscriber.test.d.ts +7 -0
- package/dist/realtime-subscriber.test.d.ts.map +1 -0
- package/dist/realtime-subscriber.test.js +183 -0
- package/dist/realtime-subscriber.test.js.map +1 -0
- package/dist/reconnection-manager.d.ts +88 -0
- package/dist/reconnection-manager.d.ts.map +1 -0
- package/dist/reconnection-manager.js +229 -0
- package/dist/reconnection-manager.js.map +1 -0
- package/dist/reconnection-manager.test.d.ts +8 -0
- package/dist/reconnection-manager.test.d.ts.map +1 -0
- package/dist/reconnection-manager.test.js +151 -0
- package/dist/reconnection-manager.test.js.map +1 -0
- package/dist/remote-sync-handler.d.ts +61 -0
- package/dist/remote-sync-handler.d.ts.map +1 -0
- package/dist/remote-sync-handler.js +197 -0
- package/dist/remote-sync-handler.js.map +1 -0
- package/dist/remote-sync-handler.test.d.ts +7 -0
- package/dist/remote-sync-handler.test.d.ts.map +1 -0
- package/dist/remote-sync-handler.test.js +212 -0
- package/dist/remote-sync-handler.test.js.map +1 -0
- package/dist/retry.d.ts +35 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +63 -0
- package/dist/retry.js.map +1 -0
- package/dist/retry.test.d.ts +5 -0
- package/dist/retry.test.d.ts.map +1 -0
- package/dist/retry.test.js +84 -0
- package/dist/retry.test.js.map +1 -0
- package/dist/storage-client.d.ts +69 -0
- package/dist/storage-client.d.ts.map +1 -0
- package/dist/storage-client.js +168 -0
- package/dist/storage-client.js.map +1 -0
- package/dist/storage-client.test.d.ts +7 -0
- package/dist/storage-client.test.d.ts.map +1 -0
- package/dist/storage-client.test.js +126 -0
- package/dist/storage-client.test.js.map +1 -0
- package/dist/supabase.d.ts +82 -0
- package/dist/supabase.d.ts.map +1 -0
- package/dist/supabase.js +341 -0
- package/dist/supabase.js.map +1 -0
- package/dist/supabase.test.d.ts +7 -0
- package/dist/supabase.test.d.ts.map +1 -0
- package/dist/supabase.test.js +273 -0
- package/dist/supabase.test.js.map +1 -0
- package/dist/sync-engine.d.ts +84 -0
- package/dist/sync-engine.d.ts.map +1 -0
- package/dist/sync-engine.js +251 -0
- package/dist/sync-engine.js.map +1 -0
- package/dist/sync-engine.test.d.ts +7 -0
- package/dist/sync-engine.test.d.ts.map +1 -0
- package/dist/sync-engine.test.js +241 -0
- package/dist/sync-engine.test.js.map +1 -0
- package/dist/sync-state.d.ts +82 -0
- package/dist/sync-state.d.ts.map +1 -0
- package/dist/sync-state.js +145 -0
- package/dist/sync-state.js.map +1 -0
- package/dist/sync-state.test.d.ts +7 -0
- package/dist/sync-state.test.d.ts.map +1 -0
- package/dist/sync-state.test.js +129 -0
- package/dist/sync-state.test.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/types.test.d.ts +7 -0
- package/dist/types.test.d.ts.map +1 -0
- package/dist/types.test.js +73 -0
- package/dist/types.test.js.map +1 -0
- package/dist/watcher.d.ts +55 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +214 -0
- package/dist/watcher.js.map +1 -0
- package/dist/watcher.test.d.ts +8 -0
- package/dist/watcher.test.d.ts.map +1 -0
- package/dist/watcher.test.js +164 -0
- package/dist/watcher.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for SyncEngine
|
|
3
|
+
*
|
|
4
|
+
* Tests batching, debouncing, priority queue, duplicate detection, and event storm handling.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
7
|
+
import { SyncEngine } from './sync-engine.js';
|
|
8
|
+
describe('SyncEngine', () => {
|
|
9
|
+
let mockWatcher;
|
|
10
|
+
let mockSupabase;
|
|
11
|
+
let mockLogger;
|
|
12
|
+
let config;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Create mock config
|
|
15
|
+
config = {
|
|
16
|
+
supabase_url: 'https://test.supabase.co',
|
|
17
|
+
supabase_key: 'test-key',
|
|
18
|
+
watch_dirs: ['/test'],
|
|
19
|
+
ignored_patterns: [],
|
|
20
|
+
debounce_ms: 100,
|
|
21
|
+
batch_size: 50,
|
|
22
|
+
log_level: 'DEBUG',
|
|
23
|
+
log_file: '/tmp/test.log',
|
|
24
|
+
log_rotation_days: 7
|
|
25
|
+
};
|
|
26
|
+
// Create mock logger
|
|
27
|
+
mockLogger = {
|
|
28
|
+
error: vi.fn(),
|
|
29
|
+
warn: vi.fn(),
|
|
30
|
+
info: vi.fn(),
|
|
31
|
+
debug: vi.fn()
|
|
32
|
+
};
|
|
33
|
+
// Create mock watcher
|
|
34
|
+
mockWatcher = {
|
|
35
|
+
on: vi.fn(),
|
|
36
|
+
start: vi.fn(),
|
|
37
|
+
stop: vi.fn()
|
|
38
|
+
};
|
|
39
|
+
// Create mock Supabase client
|
|
40
|
+
mockSupabase = {
|
|
41
|
+
syncFileBatch: vi.fn().mockResolvedValue({ success: true }),
|
|
42
|
+
heartbeat: vi.fn().mockResolvedValue({ success: true }),
|
|
43
|
+
connect: vi.fn().mockResolvedValue({ success: true }),
|
|
44
|
+
disconnect: vi.fn().mockResolvedValue(undefined)
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
it('should enqueue file change events from watcher', async () => {
|
|
48
|
+
const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
|
|
49
|
+
engine.start();
|
|
50
|
+
// Get the change handler registered with watcher
|
|
51
|
+
expect(mockWatcher.on).toHaveBeenCalledWith('change', expect.any(Function));
|
|
52
|
+
const changeHandler = mockWatcher.on.mock.calls[0][1];
|
|
53
|
+
// Create test event
|
|
54
|
+
const event = {
|
|
55
|
+
id: 'test-1',
|
|
56
|
+
workspace_id: 'ws-1',
|
|
57
|
+
file_path: '.planning/test.md',
|
|
58
|
+
event_type: 'change',
|
|
59
|
+
content_hash: 'hash1',
|
|
60
|
+
content: 'test content',
|
|
61
|
+
source: 'cli',
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
metadata: {
|
|
64
|
+
size: 100,
|
|
65
|
+
mtime: new Date().toISOString(),
|
|
66
|
+
agent_version: '0.1.0'
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
await changeHandler(event);
|
|
70
|
+
const state = engine.getState();
|
|
71
|
+
expect(state.sync_queue.length).toBeGreaterThan(0);
|
|
72
|
+
});
|
|
73
|
+
it('should batch multiple events and sync as single transaction', async () => {
|
|
74
|
+
const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
|
|
75
|
+
engine.start();
|
|
76
|
+
const changeHandler = mockWatcher.on.mock.calls[0][1];
|
|
77
|
+
// Enqueue multiple events
|
|
78
|
+
const events = [];
|
|
79
|
+
for (let i = 0; i < 5; i++) {
|
|
80
|
+
const event = {
|
|
81
|
+
id: `test-${i}`,
|
|
82
|
+
workspace_id: 'ws-1',
|
|
83
|
+
file_path: `.planning/file${i}.md`,
|
|
84
|
+
event_type: 'change',
|
|
85
|
+
content_hash: `hash${i}`,
|
|
86
|
+
content: `content ${i}`,
|
|
87
|
+
source: 'cli',
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
metadata: {
|
|
90
|
+
size: 100,
|
|
91
|
+
mtime: new Date().toISOString(),
|
|
92
|
+
agent_version: '0.1.0'
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
events.push(event);
|
|
96
|
+
await changeHandler(event);
|
|
97
|
+
}
|
|
98
|
+
// Wait for debounce + flush
|
|
99
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
100
|
+
// Should have called syncFileBatch once with all events
|
|
101
|
+
expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(1);
|
|
102
|
+
expect(mockSupabase.syncFileBatch.mock.calls[0][0].length).toBe(5);
|
|
103
|
+
});
|
|
104
|
+
it('should respect batch_size limit (max 50 files per batch)', async () => {
|
|
105
|
+
const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
|
|
106
|
+
engine.start();
|
|
107
|
+
const changeHandler = mockWatcher.on.mock.calls[0][1];
|
|
108
|
+
// Enqueue 75 events (should split into 2 batches: 50 + 25)
|
|
109
|
+
for (let i = 0; i < 75; i++) {
|
|
110
|
+
const event = {
|
|
111
|
+
id: `test-${i}`,
|
|
112
|
+
workspace_id: 'ws-1',
|
|
113
|
+
file_path: `.planning/file${i}.md`,
|
|
114
|
+
event_type: 'change',
|
|
115
|
+
content_hash: `hash${i}`,
|
|
116
|
+
content: `content ${i}`,
|
|
117
|
+
source: 'cli',
|
|
118
|
+
timestamp: new Date().toISOString(),
|
|
119
|
+
metadata: {
|
|
120
|
+
size: 100,
|
|
121
|
+
mtime: new Date().toISOString(),
|
|
122
|
+
agent_version: '0.1.0'
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
await changeHandler(event);
|
|
126
|
+
}
|
|
127
|
+
// Wait for debounce + flush
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
129
|
+
// Should have called syncFileBatch twice
|
|
130
|
+
expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(2);
|
|
131
|
+
expect(mockSupabase.syncFileBatch.mock.calls[0][0].length).toBe(50);
|
|
132
|
+
expect(mockSupabase.syncFileBatch.mock.calls[1][0].length).toBe(25);
|
|
133
|
+
});
|
|
134
|
+
it('should prioritize STATE.md and ROADMAP.md changes (high priority queue)', async () => {
|
|
135
|
+
const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
|
|
136
|
+
engine.start();
|
|
137
|
+
const changeHandler = mockWatcher.on.mock.calls[0][1];
|
|
138
|
+
// Enqueue normal file first
|
|
139
|
+
const normalEvent = {
|
|
140
|
+
id: 'normal-1',
|
|
141
|
+
workspace_id: 'ws-1',
|
|
142
|
+
file_path: '.planning/phases/01/PLAN.md',
|
|
143
|
+
event_type: 'change',
|
|
144
|
+
content_hash: 'hash-normal',
|
|
145
|
+
content: 'normal content',
|
|
146
|
+
source: 'cli',
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
metadata: {
|
|
149
|
+
size: 100,
|
|
150
|
+
mtime: new Date().toISOString(),
|
|
151
|
+
agent_version: '0.1.0'
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
await changeHandler(normalEvent);
|
|
155
|
+
// Enqueue STATE.md (should jump to front)
|
|
156
|
+
const stateEvent = {
|
|
157
|
+
id: 'state-1',
|
|
158
|
+
workspace_id: 'ws-1',
|
|
159
|
+
file_path: '.planning/STATE.md',
|
|
160
|
+
event_type: 'change',
|
|
161
|
+
content_hash: 'hash-state',
|
|
162
|
+
content: 'state content',
|
|
163
|
+
source: 'cli',
|
|
164
|
+
timestamp: new Date().toISOString(),
|
|
165
|
+
metadata: {
|
|
166
|
+
size: 100,
|
|
167
|
+
mtime: new Date().toISOString(),
|
|
168
|
+
agent_version: '0.1.0'
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
await changeHandler(stateEvent);
|
|
172
|
+
// Wait for debounce + flush
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
174
|
+
// STATE.md should be synced first
|
|
175
|
+
const syncedBatch = mockSupabase.syncFileBatch.mock.calls[0][0];
|
|
176
|
+
expect(syncedBatch[0].file_path).toBe('.planning/STATE.md');
|
|
177
|
+
});
|
|
178
|
+
it('should track synced file hashes to avoid duplicate syncs', async () => {
|
|
179
|
+
const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
|
|
180
|
+
engine.start();
|
|
181
|
+
const changeHandler = mockWatcher.on.mock.calls[0][1];
|
|
182
|
+
// Enqueue same file twice with same hash
|
|
183
|
+
const event1 = {
|
|
184
|
+
id: 'test-1',
|
|
185
|
+
workspace_id: 'ws-1',
|
|
186
|
+
file_path: '.planning/test.md',
|
|
187
|
+
event_type: 'change',
|
|
188
|
+
content_hash: 'same-hash',
|
|
189
|
+
content: 'content',
|
|
190
|
+
source: 'cli',
|
|
191
|
+
timestamp: new Date().toISOString(),
|
|
192
|
+
metadata: {
|
|
193
|
+
size: 100,
|
|
194
|
+
mtime: new Date().toISOString(),
|
|
195
|
+
agent_version: '0.1.0'
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
await changeHandler(event1);
|
|
199
|
+
// Wait for sync
|
|
200
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
201
|
+
// Enqueue same file again with same hash
|
|
202
|
+
const event2 = {
|
|
203
|
+
...event1,
|
|
204
|
+
id: 'test-2',
|
|
205
|
+
timestamp: new Date().toISOString()
|
|
206
|
+
};
|
|
207
|
+
await changeHandler(event2);
|
|
208
|
+
// Wait for potential sync
|
|
209
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
210
|
+
// Should only sync once (duplicate skipped)
|
|
211
|
+
expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(1);
|
|
212
|
+
});
|
|
213
|
+
it('should flush queue after debounce_ms delay with no new events', async () => {
|
|
214
|
+
const engine = new SyncEngine(mockWatcher, mockSupabase, config, mockLogger);
|
|
215
|
+
engine.start();
|
|
216
|
+
const changeHandler = mockWatcher.on.mock.calls[0][1];
|
|
217
|
+
const event = {
|
|
218
|
+
id: 'test-1',
|
|
219
|
+
workspace_id: 'ws-1',
|
|
220
|
+
file_path: '.planning/test.md',
|
|
221
|
+
event_type: 'change',
|
|
222
|
+
content_hash: 'hash1',
|
|
223
|
+
content: 'content',
|
|
224
|
+
source: 'cli',
|
|
225
|
+
timestamp: new Date().toISOString(),
|
|
226
|
+
metadata: {
|
|
227
|
+
size: 100,
|
|
228
|
+
mtime: new Date().toISOString(),
|
|
229
|
+
agent_version: '0.1.0'
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
await changeHandler(event);
|
|
233
|
+
// Should not sync immediately
|
|
234
|
+
expect(mockSupabase.syncFileBatch).not.toHaveBeenCalled();
|
|
235
|
+
// Wait for debounce period
|
|
236
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
237
|
+
// Should have synced after debounce
|
|
238
|
+
expect(mockSupabase.syncFileBatch).toHaveBeenCalledTimes(1);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
//# sourceMappingURL=sync-engine.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-engine.test.js","sourceRoot":"","sources":["../src/sync-engine.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync State Management for GSD Agent
|
|
3
|
+
*
|
|
4
|
+
* Persists per-workspace sync state to disk for crash recovery.
|
|
5
|
+
* Tracks last sync timestamp, connection status, and pending changes count.
|
|
6
|
+
*/
|
|
7
|
+
import { Logger } from './logger.js';
|
|
8
|
+
/**
|
|
9
|
+
* WorkspaceSyncState represents the sync state for a single workspace
|
|
10
|
+
*
|
|
11
|
+
* Persisted to disk to enable crash recovery and resumption from last sync point.
|
|
12
|
+
*/
|
|
13
|
+
export interface WorkspaceSyncState {
|
|
14
|
+
/** Unique identifier for the workspace */
|
|
15
|
+
workspace_id: string;
|
|
16
|
+
/** ISO timestamp of last successful sync */
|
|
17
|
+
last_sync_timestamp: string;
|
|
18
|
+
/** Current connection status */
|
|
19
|
+
connection_status: 'connected' | 'disconnected' | 'reconnecting';
|
|
20
|
+
/** Number of pending changes awaiting sync */
|
|
21
|
+
pending_changes_count: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* SyncStateManager manages persistent sync state for all workspaces
|
|
25
|
+
*
|
|
26
|
+
* State is stored in ~/.gsd-agent/sync-state.json and loaded on agent startup.
|
|
27
|
+
* Updates are written immediately to disk for crash recovery.
|
|
28
|
+
*/
|
|
29
|
+
export declare class SyncStateManager {
|
|
30
|
+
private stateFilePath;
|
|
31
|
+
private workspaceStates;
|
|
32
|
+
private logger;
|
|
33
|
+
constructor(configDir: string, logger: Logger);
|
|
34
|
+
/**
|
|
35
|
+
* Load sync state from disk
|
|
36
|
+
*
|
|
37
|
+
* Reads from sync-state.json and populates in-memory state map.
|
|
38
|
+
* If file doesn't exist or is invalid, starts with empty state.
|
|
39
|
+
*/
|
|
40
|
+
loadState(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Save sync state to disk
|
|
43
|
+
*
|
|
44
|
+
* Writes current state map to sync-state.json atomically.
|
|
45
|
+
* Creates parent directory if needed.
|
|
46
|
+
*/
|
|
47
|
+
saveState(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Update workspace state
|
|
50
|
+
*
|
|
51
|
+
* Merges updates into existing state or creates new state with defaults.
|
|
52
|
+
* Saves to disk immediately for crash recovery.
|
|
53
|
+
*
|
|
54
|
+
* @param workspace_id Workspace identifier
|
|
55
|
+
* @param updates Partial state updates to merge
|
|
56
|
+
*/
|
|
57
|
+
updateWorkspaceState(workspace_id: string, updates: Partial<Omit<WorkspaceSyncState, 'workspace_id'>>): void;
|
|
58
|
+
/**
|
|
59
|
+
* Get workspace state
|
|
60
|
+
*
|
|
61
|
+
* Returns state for specified workspace or default state if not found.
|
|
62
|
+
*
|
|
63
|
+
* @param workspace_id Workspace identifier
|
|
64
|
+
* @returns WorkspaceSyncState for the workspace
|
|
65
|
+
*/
|
|
66
|
+
getWorkspaceState(workspace_id: string): WorkspaceSyncState;
|
|
67
|
+
/**
|
|
68
|
+
* Get all workspace states
|
|
69
|
+
*
|
|
70
|
+
* @returns Array of all WorkspaceSyncState objects
|
|
71
|
+
*/
|
|
72
|
+
getAllWorkspaceStates(): WorkspaceSyncState[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Factory function to create SyncStateManager
|
|
76
|
+
*
|
|
77
|
+
* @param configDir Directory containing sync-state.json (e.g., ~/.gsd-agent)
|
|
78
|
+
* @param logger Logger instance
|
|
79
|
+
* @returns SyncStateManager instance
|
|
80
|
+
*/
|
|
81
|
+
export declare function createSyncStateManager(configDir: string, logger: Logger): SyncStateManager;
|
|
82
|
+
//# sourceMappingURL=sync-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-state.d.ts","sourceRoot":"","sources":["../src/sync-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,iBAAiB,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,CAAA;IAChE,8CAA8C;IAC9C,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,MAAM,CAAQ;gBAEV,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAM7C;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IAwBjB;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IA0BjB;;;;;;;;OAQG;IACH,oBAAoB,CAClB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC,GACzD,IAAI;IAkBP;;;;;;;OAOG;IACH,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB;IAe3D;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,EAAE;CAG9C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAE1F"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync State Management for GSD Agent
|
|
3
|
+
*
|
|
4
|
+
* Persists per-workspace sync state to disk for crash recovery.
|
|
5
|
+
* Tracks last sync timestamp, connection status, and pending changes count.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
/**
|
|
10
|
+
* SyncStateManager manages persistent sync state for all workspaces
|
|
11
|
+
*
|
|
12
|
+
* State is stored in ~/.gsd-agent/sync-state.json and loaded on agent startup.
|
|
13
|
+
* Updates are written immediately to disk for crash recovery.
|
|
14
|
+
*/
|
|
15
|
+
export class SyncStateManager {
|
|
16
|
+
stateFilePath;
|
|
17
|
+
workspaceStates;
|
|
18
|
+
logger;
|
|
19
|
+
constructor(configDir, logger) {
|
|
20
|
+
this.stateFilePath = path.join(configDir, 'sync-state.json');
|
|
21
|
+
this.workspaceStates = new Map();
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Load sync state from disk
|
|
26
|
+
*
|
|
27
|
+
* Reads from sync-state.json and populates in-memory state map.
|
|
28
|
+
* If file doesn't exist or is invalid, starts with empty state.
|
|
29
|
+
*/
|
|
30
|
+
loadState() {
|
|
31
|
+
if (!fs.existsSync(this.stateFilePath)) {
|
|
32
|
+
this.logger.info('No existing sync state file found, starting fresh');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const fileContent = fs.readFileSync(this.stateFilePath, 'utf-8');
|
|
37
|
+
const parsed = JSON.parse(fileContent);
|
|
38
|
+
if (parsed.workspaces && typeof parsed.workspaces === 'object') {
|
|
39
|
+
for (const [workspaceId, state] of Object.entries(parsed.workspaces)) {
|
|
40
|
+
this.workspaceStates.set(workspaceId, state);
|
|
41
|
+
}
|
|
42
|
+
this.logger.info(`Loaded sync state for ${this.workspaceStates.size} workspaces`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
this.logger.warn(`Failed to load sync state from ${this.stateFilePath}:`, {
|
|
47
|
+
error: error instanceof Error ? error.message : String(error)
|
|
48
|
+
});
|
|
49
|
+
this.logger.warn('Starting with empty sync state');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Save sync state to disk
|
|
54
|
+
*
|
|
55
|
+
* Writes current state map to sync-state.json atomically.
|
|
56
|
+
* Creates parent directory if needed.
|
|
57
|
+
*/
|
|
58
|
+
saveState() {
|
|
59
|
+
try {
|
|
60
|
+
// Create parent directory if needed
|
|
61
|
+
const parentDir = path.dirname(this.stateFilePath);
|
|
62
|
+
if (!fs.existsSync(parentDir)) {
|
|
63
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
// Convert Map to plain object for JSON serialization
|
|
66
|
+
const stateData = {
|
|
67
|
+
workspaces: Object.fromEntries(this.workspaceStates.entries())
|
|
68
|
+
};
|
|
69
|
+
// Atomic write: write to temp file, then rename
|
|
70
|
+
const tempFilePath = `${this.stateFilePath}.tmp`;
|
|
71
|
+
fs.writeFileSync(tempFilePath, JSON.stringify(stateData, null, 2), 'utf-8');
|
|
72
|
+
fs.renameSync(tempFilePath, this.stateFilePath);
|
|
73
|
+
this.logger.debug(`Saved sync state for ${this.workspaceStates.size} workspaces`);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.logger.error(`Failed to save sync state to ${this.stateFilePath}:`, {
|
|
77
|
+
error: error instanceof Error ? error.message : String(error)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Update workspace state
|
|
83
|
+
*
|
|
84
|
+
* Merges updates into existing state or creates new state with defaults.
|
|
85
|
+
* Saves to disk immediately for crash recovery.
|
|
86
|
+
*
|
|
87
|
+
* @param workspace_id Workspace identifier
|
|
88
|
+
* @param updates Partial state updates to merge
|
|
89
|
+
*/
|
|
90
|
+
updateWorkspaceState(workspace_id, updates) {
|
|
91
|
+
const existingState = this.workspaceStates.get(workspace_id) || {
|
|
92
|
+
workspace_id,
|
|
93
|
+
last_sync_timestamp: new Date().toISOString(),
|
|
94
|
+
connection_status: 'disconnected',
|
|
95
|
+
pending_changes_count: 0
|
|
96
|
+
};
|
|
97
|
+
const updatedState = {
|
|
98
|
+
...existingState,
|
|
99
|
+
...updates,
|
|
100
|
+
workspace_id // Ensure workspace_id is never overwritten
|
|
101
|
+
};
|
|
102
|
+
this.workspaceStates.set(workspace_id, updatedState);
|
|
103
|
+
this.saveState();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get workspace state
|
|
107
|
+
*
|
|
108
|
+
* Returns state for specified workspace or default state if not found.
|
|
109
|
+
*
|
|
110
|
+
* @param workspace_id Workspace identifier
|
|
111
|
+
* @returns WorkspaceSyncState for the workspace
|
|
112
|
+
*/
|
|
113
|
+
getWorkspaceState(workspace_id) {
|
|
114
|
+
const state = this.workspaceStates.get(workspace_id);
|
|
115
|
+
if (state) {
|
|
116
|
+
return state;
|
|
117
|
+
}
|
|
118
|
+
// Return default state for unknown workspace
|
|
119
|
+
return {
|
|
120
|
+
workspace_id,
|
|
121
|
+
last_sync_timestamp: new Date().toISOString(),
|
|
122
|
+
connection_status: 'disconnected',
|
|
123
|
+
pending_changes_count: 0
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get all workspace states
|
|
128
|
+
*
|
|
129
|
+
* @returns Array of all WorkspaceSyncState objects
|
|
130
|
+
*/
|
|
131
|
+
getAllWorkspaceStates() {
|
|
132
|
+
return Array.from(this.workspaceStates.values());
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Factory function to create SyncStateManager
|
|
137
|
+
*
|
|
138
|
+
* @param configDir Directory containing sync-state.json (e.g., ~/.gsd-agent)
|
|
139
|
+
* @param logger Logger instance
|
|
140
|
+
* @returns SyncStateManager instance
|
|
141
|
+
*/
|
|
142
|
+
export function createSyncStateManager(configDir, logger) {
|
|
143
|
+
return new SyncStateManager(configDir, logger);
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=sync-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-state.js","sourceRoot":"","sources":["../src/sync-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-state.test.d.ts","sourceRoot":"","sources":["../src/sync-state.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SyncStateManager
|
|
3
|
+
*
|
|
4
|
+
* Validates persistent sync state management for crash recovery.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import { createSyncStateManager } from './sync-state.js';
|
|
11
|
+
import { createLogger } from './logger.js';
|
|
12
|
+
describe('SyncStateManager', () => {
|
|
13
|
+
let tempDir;
|
|
14
|
+
let stateFilePath;
|
|
15
|
+
let logger;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Create temporary directory for test state files
|
|
18
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-agent-test-'));
|
|
19
|
+
stateFilePath = path.join(tempDir, 'sync-state.json');
|
|
20
|
+
logger = createLogger({
|
|
21
|
+
log_level: 'ERROR',
|
|
22
|
+
log_file: path.join(tempDir, 'test.log'),
|
|
23
|
+
log_rotation_days: 1
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
// Clean up temporary directory
|
|
28
|
+
if (fs.existsSync(tempDir)) {
|
|
29
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
it('Test 1: WorkspaceSyncState has required fields', () => {
|
|
33
|
+
const manager = createSyncStateManager(tempDir, logger);
|
|
34
|
+
const state = manager.getWorkspaceState('workspace-123');
|
|
35
|
+
expect(state).toHaveProperty('workspace_id');
|
|
36
|
+
expect(state).toHaveProperty('last_sync_timestamp');
|
|
37
|
+
expect(state).toHaveProperty('connection_status');
|
|
38
|
+
expect(state).toHaveProperty('pending_changes_count');
|
|
39
|
+
expect(state.workspace_id).toBe('workspace-123');
|
|
40
|
+
expect(state.connection_status).toBe('disconnected');
|
|
41
|
+
expect(state.pending_changes_count).toBe(0);
|
|
42
|
+
});
|
|
43
|
+
it('Test 2: saveState() writes sync state to disk', () => {
|
|
44
|
+
const manager = createSyncStateManager(tempDir, logger);
|
|
45
|
+
manager.updateWorkspaceState('workspace-123', {
|
|
46
|
+
last_sync_timestamp: '2026-03-27T12:00:00Z',
|
|
47
|
+
connection_status: 'connected',
|
|
48
|
+
pending_changes_count: 5
|
|
49
|
+
});
|
|
50
|
+
manager.saveState();
|
|
51
|
+
expect(fs.existsSync(stateFilePath)).toBe(true);
|
|
52
|
+
const fileContent = fs.readFileSync(stateFilePath, 'utf-8');
|
|
53
|
+
const parsed = JSON.parse(fileContent);
|
|
54
|
+
expect(parsed).toHaveProperty('workspaces');
|
|
55
|
+
expect(parsed.workspaces['workspace-123']).toBeDefined();
|
|
56
|
+
expect(parsed.workspaces['workspace-123'].last_sync_timestamp).toBe('2026-03-27T12:00:00Z');
|
|
57
|
+
expect(parsed.workspaces['workspace-123'].connection_status).toBe('connected');
|
|
58
|
+
expect(parsed.workspaces['workspace-123'].pending_changes_count).toBe(5);
|
|
59
|
+
});
|
|
60
|
+
it('Test 3: loadState() reads sync state from disk', () => {
|
|
61
|
+
// Write state file manually
|
|
62
|
+
const stateData = {
|
|
63
|
+
workspaces: {
|
|
64
|
+
'workspace-456': {
|
|
65
|
+
workspace_id: 'workspace-456',
|
|
66
|
+
last_sync_timestamp: '2026-03-27T13:00:00Z',
|
|
67
|
+
connection_status: 'connected',
|
|
68
|
+
pending_changes_count: 10
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
fs.writeFileSync(stateFilePath, JSON.stringify(stateData, null, 2));
|
|
73
|
+
const manager = createSyncStateManager(tempDir, logger);
|
|
74
|
+
manager.loadState();
|
|
75
|
+
const state = manager.getWorkspaceState('workspace-456');
|
|
76
|
+
expect(state.workspace_id).toBe('workspace-456');
|
|
77
|
+
expect(state.last_sync_timestamp).toBe('2026-03-27T13:00:00Z');
|
|
78
|
+
expect(state.connection_status).toBe('connected');
|
|
79
|
+
expect(state.pending_changes_count).toBe(10);
|
|
80
|
+
});
|
|
81
|
+
it('Test 4: updateWorkspaceState() updates specific workspace state', () => {
|
|
82
|
+
const manager = createSyncStateManager(tempDir, logger);
|
|
83
|
+
manager.updateWorkspaceState('workspace-789', {
|
|
84
|
+
connection_status: 'reconnecting',
|
|
85
|
+
pending_changes_count: 3
|
|
86
|
+
});
|
|
87
|
+
const state = manager.getWorkspaceState('workspace-789');
|
|
88
|
+
expect(state.connection_status).toBe('reconnecting');
|
|
89
|
+
expect(state.pending_changes_count).toBe(3);
|
|
90
|
+
expect(state.workspace_id).toBe('workspace-789');
|
|
91
|
+
});
|
|
92
|
+
it('Test 5: getWorkspaceState() retrieves state for specific workspace', () => {
|
|
93
|
+
const manager = createSyncStateManager(tempDir, logger);
|
|
94
|
+
manager.updateWorkspaceState('workspace-abc', {
|
|
95
|
+
last_sync_timestamp: '2026-03-27T14:00:00Z',
|
|
96
|
+
connection_status: 'connected',
|
|
97
|
+
pending_changes_count: 0
|
|
98
|
+
});
|
|
99
|
+
const state = manager.getWorkspaceState('workspace-abc');
|
|
100
|
+
expect(state.workspace_id).toBe('workspace-abc');
|
|
101
|
+
expect(state.last_sync_timestamp).toBe('2026-03-27T14:00:00Z');
|
|
102
|
+
expect(state.connection_status).toBe('connected');
|
|
103
|
+
expect(state.pending_changes_count).toBe(0);
|
|
104
|
+
// Non-existent workspace returns default state
|
|
105
|
+
const defaultState = manager.getWorkspaceState('workspace-nonexistent');
|
|
106
|
+
expect(defaultState.workspace_id).toBe('workspace-nonexistent');
|
|
107
|
+
expect(defaultState.connection_status).toBe('disconnected');
|
|
108
|
+
expect(defaultState.pending_changes_count).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
it('Test 6: On agent restart, loadState() resumes from last_sync_timestamp', () => {
|
|
111
|
+
// First manager instance - create and save state
|
|
112
|
+
const manager1 = createSyncStateManager(tempDir, logger);
|
|
113
|
+
manager1.updateWorkspaceState('workspace-restart', {
|
|
114
|
+
last_sync_timestamp: '2026-03-27T15:00:00Z',
|
|
115
|
+
connection_status: 'connected',
|
|
116
|
+
pending_changes_count: 7
|
|
117
|
+
});
|
|
118
|
+
manager1.saveState();
|
|
119
|
+
// Simulate agent restart - create new manager instance
|
|
120
|
+
const manager2 = createSyncStateManager(tempDir, logger);
|
|
121
|
+
manager2.loadState();
|
|
122
|
+
const state = manager2.getWorkspaceState('workspace-restart');
|
|
123
|
+
expect(state.last_sync_timestamp).toBe('2026-03-27T15:00:00Z');
|
|
124
|
+
expect(state.pending_changes_count).toBe(7);
|
|
125
|
+
// Connection status should be reset to disconnected on restart
|
|
126
|
+
expect(state.connection_status).toBe('connected');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=sync-state.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-state.test.js","sourceRoot":"","sources":["../src/sync-state.test.ts"],"names":[],"mappings":"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"}
|