@siftd/connect-agent 0.2.8 → 0.2.9
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/heartbeat.js +1 -1
- package/dist/orchestrator.d.ts +7 -2
- package/dist/orchestrator.js +64 -10
- package/dist/workers/shared-state.d.ts +90 -0
- package/dist/workers/shared-state.js +254 -0
- package/package.json +1 -1
package/dist/heartbeat.js
CHANGED
|
@@ -10,7 +10,7 @@ import { hostname } from 'os';
|
|
|
10
10
|
import { createHash } from 'crypto';
|
|
11
11
|
import { getServerUrl, getAgentToken, getUserId, isCloudMode } from './config.js';
|
|
12
12
|
const HEARTBEAT_INTERVAL = 10000; // 10 seconds
|
|
13
|
-
const VERSION = '0.2.
|
|
13
|
+
const VERSION = '0.2.9'; // Should match package.json
|
|
14
14
|
const state = {
|
|
15
15
|
intervalId: null,
|
|
16
16
|
runnerId: null,
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class MasterOrchestrator {
|
|
|
22
22
|
private bashTool;
|
|
23
23
|
private webTools;
|
|
24
24
|
private workerTools;
|
|
25
|
+
private sharedState;
|
|
25
26
|
private verboseMode;
|
|
26
27
|
constructor(options: {
|
|
27
28
|
apiKey: string;
|
|
@@ -78,8 +79,12 @@ export declare class MasterOrchestrator {
|
|
|
78
79
|
*/
|
|
79
80
|
private delegateToWorker;
|
|
80
81
|
/**
|
|
81
|
-
* Extract memory contributions from worker output
|
|
82
|
-
* Workers can contribute
|
|
82
|
+
* Extract memory and coordination contributions from worker output
|
|
83
|
+
* Workers can contribute using:
|
|
84
|
+
* - [MEMORY] type=X | content=Y
|
|
85
|
+
* - [SHARE] key=X | value=Y
|
|
86
|
+
* - [SIGNAL] name=X | data=Y
|
|
87
|
+
* - [MESSAGE] to=X | content=Y
|
|
83
88
|
*/
|
|
84
89
|
private extractWorkerMemories;
|
|
85
90
|
/**
|
package/dist/orchestrator.js
CHANGED
|
@@ -13,6 +13,7 @@ import { SystemIndexer } from './core/system-indexer.js';
|
|
|
13
13
|
import { BashTool } from './tools/bash.js';
|
|
14
14
|
import { WebTools } from './tools/web.js';
|
|
15
15
|
import { WorkerTools } from './tools/worker.js';
|
|
16
|
+
import { SharedState } from './workers/shared-state.js';
|
|
16
17
|
import { getKnowledgeForPrompt } from './genesis/index.js';
|
|
17
18
|
const SYSTEM_PROMPT = `You're the user's personal assistant - the friendly brain behind their Connect app. You chat with them from any browser, remember everything about them, and dispatch Claude Code workers to do things on their machine.
|
|
18
19
|
|
|
@@ -85,6 +86,7 @@ export class MasterOrchestrator {
|
|
|
85
86
|
bashTool;
|
|
86
87
|
webTools;
|
|
87
88
|
workerTools;
|
|
89
|
+
sharedState;
|
|
88
90
|
verboseMode = new Map(); // per-user verbose mode
|
|
89
91
|
constructor(options) {
|
|
90
92
|
this.client = new Anthropic({ apiKey: options.apiKey });
|
|
@@ -108,6 +110,7 @@ export class MasterOrchestrator {
|
|
|
108
110
|
this.bashTool = new BashTool(this.workspaceDir);
|
|
109
111
|
this.webTools = new WebTools();
|
|
110
112
|
this.workerTools = new WorkerTools(this.workspaceDir);
|
|
113
|
+
this.sharedState = new SharedState(this.workspaceDir);
|
|
111
114
|
}
|
|
112
115
|
/**
|
|
113
116
|
* Initialize the orchestrator - indexes filesystem on first call
|
|
@@ -703,6 +706,8 @@ Be specific about what you want done.`,
|
|
|
703
706
|
// Memory search failed, continue without it
|
|
704
707
|
console.log('[ORCHESTRATOR] Memory search failed, continuing without context');
|
|
705
708
|
}
|
|
709
|
+
// Get shared state context for worker coordination
|
|
710
|
+
const sharedContext = this.sharedState.getSummaryForWorker(id);
|
|
706
711
|
// Build prompt for worker with memory context and checkpoint instructions
|
|
707
712
|
let prompt = task;
|
|
708
713
|
if (context) {
|
|
@@ -711,6 +716,9 @@ Be specific about what you want done.`,
|
|
|
711
716
|
if (memoryContext) {
|
|
712
717
|
prompt += memoryContext;
|
|
713
718
|
}
|
|
719
|
+
if (sharedContext) {
|
|
720
|
+
prompt += `\n\nWORKER COORDINATION:${sharedContext}`;
|
|
721
|
+
}
|
|
714
722
|
// Add checkpoint and logging instructions to prevent data loss
|
|
715
723
|
const logFile = `/tmp/worker-${id}-log.txt`;
|
|
716
724
|
prompt += `
|
|
@@ -730,7 +738,14 @@ If you discover something important (patterns, user preferences, technical insig
|
|
|
730
738
|
end your response with a line like:
|
|
731
739
|
[MEMORY] type=semantic | content=User prefers X over Y for this type of task
|
|
732
740
|
[MEMORY] type=procedural | content=When doing X, always check Y first
|
|
733
|
-
This helps me remember and improve for future tasks
|
|
741
|
+
This helps me remember and improve for future tasks.
|
|
742
|
+
|
|
743
|
+
WORKER COORDINATION (optional):
|
|
744
|
+
If you need to share data with other workers or signal completion:
|
|
745
|
+
[SHARE] key=myData | value={"result": "something useful"}
|
|
746
|
+
[SIGNAL] name=step1_complete | data={"files": ["a.ts", "b.ts"]}
|
|
747
|
+
[MESSAGE] to=worker_xyz | content=Please review the changes I made
|
|
748
|
+
This enables parallel workers to coordinate.`;
|
|
734
749
|
console.log(`[ORCHESTRATOR] Delegating to worker ${id}: ${task.slice(0, 80)}...`);
|
|
735
750
|
return new Promise((resolve) => {
|
|
736
751
|
const job = {
|
|
@@ -822,37 +837,76 @@ This helps me remember and improve for future tasks.`;
|
|
|
822
837
|
});
|
|
823
838
|
}
|
|
824
839
|
/**
|
|
825
|
-
* Extract memory contributions from worker output
|
|
826
|
-
* Workers can contribute
|
|
840
|
+
* Extract memory and coordination contributions from worker output
|
|
841
|
+
* Workers can contribute using:
|
|
842
|
+
* - [MEMORY] type=X | content=Y
|
|
843
|
+
* - [SHARE] key=X | value=Y
|
|
844
|
+
* - [SIGNAL] name=X | data=Y
|
|
845
|
+
* - [MESSAGE] to=X | content=Y
|
|
827
846
|
*/
|
|
828
847
|
async extractWorkerMemories(output, workerId) {
|
|
829
|
-
|
|
848
|
+
let memoryCount = 0;
|
|
849
|
+
let coordCount = 0;
|
|
850
|
+
// Extract memory contributions
|
|
830
851
|
const memoryPattern = /\[MEMORY\]\s*type=(\w+)\s*\|\s*content=(.+?)(?=\n|$)/g;
|
|
831
852
|
let match;
|
|
832
|
-
let count = 0;
|
|
833
853
|
while ((match = memoryPattern.exec(output)) !== null) {
|
|
834
854
|
const type = match[1].toLowerCase();
|
|
835
855
|
const content = match[2].trim();
|
|
836
856
|
if (!content)
|
|
837
857
|
continue;
|
|
838
|
-
// Validate memory type
|
|
839
858
|
const validTypes = ['episodic', 'semantic', 'procedural'];
|
|
840
859
|
const memType = validTypes.includes(type) ? type : 'semantic';
|
|
841
860
|
try {
|
|
842
861
|
await this.memory.remember(content, {
|
|
843
862
|
type: memType,
|
|
844
863
|
source: `worker:${workerId}`,
|
|
845
|
-
importance: 0.7,
|
|
864
|
+
importance: 0.7,
|
|
846
865
|
tags: ['worker-contributed', 'auto-learned']
|
|
847
866
|
});
|
|
848
|
-
|
|
867
|
+
memoryCount++;
|
|
849
868
|
}
|
|
850
869
|
catch (error) {
|
|
851
870
|
console.log(`[ORCHESTRATOR] Failed to store worker memory: ${error}`);
|
|
852
871
|
}
|
|
853
872
|
}
|
|
854
|
-
|
|
855
|
-
|
|
873
|
+
// Extract shared state contributions
|
|
874
|
+
const sharePattern = /\[SHARE\]\s*key=(\w+)\s*\|\s*value=(.+?)(?=\n|$)/g;
|
|
875
|
+
while ((match = sharePattern.exec(output)) !== null) {
|
|
876
|
+
const key = match[1];
|
|
877
|
+
let value = match[2].trim();
|
|
878
|
+
try {
|
|
879
|
+
value = JSON.parse(value);
|
|
880
|
+
}
|
|
881
|
+
catch { /* use as string */ }
|
|
882
|
+
this.sharedState.set(key, value, workerId);
|
|
883
|
+
coordCount++;
|
|
884
|
+
}
|
|
885
|
+
// Extract signal completions
|
|
886
|
+
const signalPattern = /\[SIGNAL\]\s*name=(\w+)(?:\s*\|\s*data=(.+?))?(?=\n|$)/g;
|
|
887
|
+
while ((match = signalPattern.exec(output)) !== null) {
|
|
888
|
+
const name = match[1];
|
|
889
|
+
let data = match[2]?.trim();
|
|
890
|
+
try {
|
|
891
|
+
data = data ? JSON.parse(data) : undefined;
|
|
892
|
+
}
|
|
893
|
+
catch { /* use as string */ }
|
|
894
|
+
this.sharedState.signalComplete(name, workerId, data);
|
|
895
|
+
coordCount++;
|
|
896
|
+
}
|
|
897
|
+
// Extract messages
|
|
898
|
+
const messagePattern = /\[MESSAGE\]\s*to=(\w+)\s*\|\s*content=(.+?)(?=\n|$)/g;
|
|
899
|
+
while ((match = messagePattern.exec(output)) !== null) {
|
|
900
|
+
const to = match[1];
|
|
901
|
+
const content = match[2].trim();
|
|
902
|
+
this.sharedState.sendMessage(workerId, to, 'data', content);
|
|
903
|
+
coordCount++;
|
|
904
|
+
}
|
|
905
|
+
if (memoryCount > 0) {
|
|
906
|
+
console.log(`[ORCHESTRATOR] Extracted ${memoryCount} memory contributions from worker ${workerId}`);
|
|
907
|
+
}
|
|
908
|
+
if (coordCount > 0) {
|
|
909
|
+
console.log(`[ORCHESTRATOR] Extracted ${coordCount} coordination messages from worker ${workerId}`);
|
|
856
910
|
}
|
|
857
911
|
}
|
|
858
912
|
/**
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared State for Worker Communication
|
|
3
|
+
*
|
|
4
|
+
* Enables workers to:
|
|
5
|
+
* - Share data via a common state store
|
|
6
|
+
* - Signal completion of dependencies
|
|
7
|
+
* - Lock resources to prevent conflicts
|
|
8
|
+
* - Pass messages between workers
|
|
9
|
+
*/
|
|
10
|
+
export interface SharedMessage {
|
|
11
|
+
from: string;
|
|
12
|
+
to: string | '*';
|
|
13
|
+
type: 'data' | 'signal' | 'request';
|
|
14
|
+
content: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ResourceLock {
|
|
18
|
+
resource: string;
|
|
19
|
+
owner: string;
|
|
20
|
+
acquired: string;
|
|
21
|
+
expires: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class SharedState {
|
|
24
|
+
private stateDir;
|
|
25
|
+
private stateFile;
|
|
26
|
+
private messagesFile;
|
|
27
|
+
private locksFile;
|
|
28
|
+
constructor(workspaceDir: string);
|
|
29
|
+
private ensureStateDir;
|
|
30
|
+
/**
|
|
31
|
+
* Set a shared value
|
|
32
|
+
*/
|
|
33
|
+
set(key: string, value: unknown, workerId?: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get a shared value
|
|
36
|
+
*/
|
|
37
|
+
get(key: string): unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Get all shared state
|
|
40
|
+
*/
|
|
41
|
+
getAll(): Record<string, unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Signal that a worker has completed a specific task/dependency
|
|
44
|
+
*/
|
|
45
|
+
signalComplete(signalName: string, workerId: string, data?: unknown): void;
|
|
46
|
+
/**
|
|
47
|
+
* Wait for a signal (polling-based)
|
|
48
|
+
* Returns true if signal is already set, false if not
|
|
49
|
+
*/
|
|
50
|
+
checkSignal(signalName: string): {
|
|
51
|
+
completed: boolean;
|
|
52
|
+
data?: unknown;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Send a message to another worker
|
|
56
|
+
*/
|
|
57
|
+
sendMessage(from: string, to: string, type: 'data' | 'signal' | 'request', content: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Get messages for a worker
|
|
60
|
+
*/
|
|
61
|
+
getMessages(workerId: string, since?: string): SharedMessage[];
|
|
62
|
+
/**
|
|
63
|
+
* Try to acquire a resource lock
|
|
64
|
+
*/
|
|
65
|
+
acquireLock(resource: string, workerId: string, durationMs?: number): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Release a resource lock
|
|
68
|
+
*/
|
|
69
|
+
releaseLock(resource: string, workerId: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Clean up expired locks
|
|
72
|
+
*/
|
|
73
|
+
cleanupExpiredLocks(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Get summary for injecting into worker prompts
|
|
76
|
+
*/
|
|
77
|
+
getSummaryForWorker(workerId: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Clear all shared state (for cleanup between sessions)
|
|
80
|
+
*/
|
|
81
|
+
clear(): void;
|
|
82
|
+
private loadState;
|
|
83
|
+
private saveState;
|
|
84
|
+
private loadSignals;
|
|
85
|
+
private saveSignals;
|
|
86
|
+
private loadMessages;
|
|
87
|
+
private saveMessages;
|
|
88
|
+
private loadLocks;
|
|
89
|
+
private saveLocks;
|
|
90
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared State for Worker Communication
|
|
3
|
+
*
|
|
4
|
+
* Enables workers to:
|
|
5
|
+
* - Share data via a common state store
|
|
6
|
+
* - Signal completion of dependencies
|
|
7
|
+
* - Lock resources to prevent conflicts
|
|
8
|
+
* - Pass messages between workers
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
export class SharedState {
|
|
13
|
+
stateDir;
|
|
14
|
+
stateFile;
|
|
15
|
+
messagesFile;
|
|
16
|
+
locksFile;
|
|
17
|
+
constructor(workspaceDir) {
|
|
18
|
+
this.stateDir = path.join(workspaceDir, '.worker-state');
|
|
19
|
+
this.stateFile = path.join(this.stateDir, 'state.json');
|
|
20
|
+
this.messagesFile = path.join(this.stateDir, 'messages.json');
|
|
21
|
+
this.locksFile = path.join(this.stateDir, 'locks.json');
|
|
22
|
+
this.ensureStateDir();
|
|
23
|
+
}
|
|
24
|
+
ensureStateDir() {
|
|
25
|
+
if (!fs.existsSync(this.stateDir)) {
|
|
26
|
+
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Set a shared value
|
|
31
|
+
*/
|
|
32
|
+
set(key, value, workerId) {
|
|
33
|
+
const state = this.loadState();
|
|
34
|
+
state[key] = {
|
|
35
|
+
value,
|
|
36
|
+
setBy: workerId || 'unknown',
|
|
37
|
+
timestamp: new Date().toISOString()
|
|
38
|
+
};
|
|
39
|
+
this.saveState(state);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a shared value
|
|
43
|
+
*/
|
|
44
|
+
get(key) {
|
|
45
|
+
const state = this.loadState();
|
|
46
|
+
return state[key]?.value;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get all shared state
|
|
50
|
+
*/
|
|
51
|
+
getAll() {
|
|
52
|
+
const state = this.loadState();
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const [key, entry] of Object.entries(state)) {
|
|
55
|
+
result[key] = entry.value;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Signal that a worker has completed a specific task/dependency
|
|
61
|
+
*/
|
|
62
|
+
signalComplete(signalName, workerId, data) {
|
|
63
|
+
const signals = this.loadSignals();
|
|
64
|
+
signals[signalName] = {
|
|
65
|
+
completed: true,
|
|
66
|
+
completedBy: workerId,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
data
|
|
69
|
+
};
|
|
70
|
+
this.saveSignals(signals);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Wait for a signal (polling-based)
|
|
74
|
+
* Returns true if signal is already set, false if not
|
|
75
|
+
*/
|
|
76
|
+
checkSignal(signalName) {
|
|
77
|
+
const signals = this.loadSignals();
|
|
78
|
+
const signal = signals[signalName];
|
|
79
|
+
if (signal?.completed) {
|
|
80
|
+
return { completed: true, data: signal.data };
|
|
81
|
+
}
|
|
82
|
+
return { completed: false };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Send a message to another worker
|
|
86
|
+
*/
|
|
87
|
+
sendMessage(from, to, type, content) {
|
|
88
|
+
const messages = this.loadMessages();
|
|
89
|
+
messages.push({
|
|
90
|
+
from,
|
|
91
|
+
to,
|
|
92
|
+
type,
|
|
93
|
+
content,
|
|
94
|
+
timestamp: new Date().toISOString()
|
|
95
|
+
});
|
|
96
|
+
// Keep last 100 messages
|
|
97
|
+
if (messages.length > 100) {
|
|
98
|
+
messages.splice(0, messages.length - 100);
|
|
99
|
+
}
|
|
100
|
+
this.saveMessages(messages);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get messages for a worker
|
|
104
|
+
*/
|
|
105
|
+
getMessages(workerId, since) {
|
|
106
|
+
const messages = this.loadMessages();
|
|
107
|
+
return messages.filter(m => {
|
|
108
|
+
const isForMe = m.to === workerId || m.to === '*';
|
|
109
|
+
const isAfterSince = !since || m.timestamp > since;
|
|
110
|
+
return isForMe && isAfterSince;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Try to acquire a resource lock
|
|
115
|
+
*/
|
|
116
|
+
acquireLock(resource, workerId, durationMs = 60000) {
|
|
117
|
+
const locks = this.loadLocks();
|
|
118
|
+
const now = new Date();
|
|
119
|
+
const existing = locks[resource];
|
|
120
|
+
// Check if existing lock is still valid
|
|
121
|
+
if (existing && new Date(existing.expires) > now && existing.owner !== workerId) {
|
|
122
|
+
return false; // Lock held by another worker
|
|
123
|
+
}
|
|
124
|
+
// Acquire lock
|
|
125
|
+
locks[resource] = {
|
|
126
|
+
resource,
|
|
127
|
+
owner: workerId,
|
|
128
|
+
acquired: now.toISOString(),
|
|
129
|
+
expires: new Date(now.getTime() + durationMs).toISOString()
|
|
130
|
+
};
|
|
131
|
+
this.saveLocks(locks);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Release a resource lock
|
|
136
|
+
*/
|
|
137
|
+
releaseLock(resource, workerId) {
|
|
138
|
+
const locks = this.loadLocks();
|
|
139
|
+
if (locks[resource]?.owner === workerId) {
|
|
140
|
+
delete locks[resource];
|
|
141
|
+
this.saveLocks(locks);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Clean up expired locks
|
|
148
|
+
*/
|
|
149
|
+
cleanupExpiredLocks() {
|
|
150
|
+
const locks = this.loadLocks();
|
|
151
|
+
const now = new Date();
|
|
152
|
+
let changed = false;
|
|
153
|
+
for (const [resource, lock] of Object.entries(locks)) {
|
|
154
|
+
if (new Date(lock.expires) < now) {
|
|
155
|
+
delete locks[resource];
|
|
156
|
+
changed = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (changed) {
|
|
160
|
+
this.saveLocks(locks);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get summary for injecting into worker prompts
|
|
165
|
+
*/
|
|
166
|
+
getSummaryForWorker(workerId) {
|
|
167
|
+
const state = this.getAll();
|
|
168
|
+
const messages = this.getMessages(workerId);
|
|
169
|
+
const signals = this.loadSignals();
|
|
170
|
+
let summary = '';
|
|
171
|
+
if (Object.keys(state).length > 0) {
|
|
172
|
+
summary += '\n## Shared State:\n';
|
|
173
|
+
for (const [key, value] of Object.entries(state)) {
|
|
174
|
+
summary += `- ${key}: ${JSON.stringify(value)}\n`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (messages.length > 0) {
|
|
178
|
+
summary += '\n## Messages for you:\n';
|
|
179
|
+
for (const msg of messages.slice(-5)) { // Last 5 messages
|
|
180
|
+
summary += `- [${msg.from}] ${msg.content}\n`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const completedSignals = Object.entries(signals).filter(([, s]) => s.completed);
|
|
184
|
+
if (completedSignals.length > 0) {
|
|
185
|
+
summary += '\n## Completed dependencies:\n';
|
|
186
|
+
for (const [name] of completedSignals) {
|
|
187
|
+
summary += `- ${name} ✓\n`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return summary;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Clear all shared state (for cleanup between sessions)
|
|
194
|
+
*/
|
|
195
|
+
clear() {
|
|
196
|
+
if (fs.existsSync(this.stateFile))
|
|
197
|
+
fs.unlinkSync(this.stateFile);
|
|
198
|
+
if (fs.existsSync(this.messagesFile))
|
|
199
|
+
fs.unlinkSync(this.messagesFile);
|
|
200
|
+
if (fs.existsSync(this.locksFile))
|
|
201
|
+
fs.unlinkSync(this.locksFile);
|
|
202
|
+
}
|
|
203
|
+
// Private helpers
|
|
204
|
+
loadState() {
|
|
205
|
+
try {
|
|
206
|
+
if (fs.existsSync(this.stateFile)) {
|
|
207
|
+
return JSON.parse(fs.readFileSync(this.stateFile, 'utf8'));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch { /* ignore */ }
|
|
211
|
+
return {};
|
|
212
|
+
}
|
|
213
|
+
saveState(state) {
|
|
214
|
+
fs.writeFileSync(this.stateFile, JSON.stringify(state, null, 2));
|
|
215
|
+
}
|
|
216
|
+
loadSignals() {
|
|
217
|
+
try {
|
|
218
|
+
const signalsFile = path.join(this.stateDir, 'signals.json');
|
|
219
|
+
if (fs.existsSync(signalsFile)) {
|
|
220
|
+
return JSON.parse(fs.readFileSync(signalsFile, 'utf8'));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch { /* ignore */ }
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
saveSignals(signals) {
|
|
227
|
+
const signalsFile = path.join(this.stateDir, 'signals.json');
|
|
228
|
+
fs.writeFileSync(signalsFile, JSON.stringify(signals, null, 2));
|
|
229
|
+
}
|
|
230
|
+
loadMessages() {
|
|
231
|
+
try {
|
|
232
|
+
if (fs.existsSync(this.messagesFile)) {
|
|
233
|
+
return JSON.parse(fs.readFileSync(this.messagesFile, 'utf8'));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch { /* ignore */ }
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
saveMessages(messages) {
|
|
240
|
+
fs.writeFileSync(this.messagesFile, JSON.stringify(messages, null, 2));
|
|
241
|
+
}
|
|
242
|
+
loadLocks() {
|
|
243
|
+
try {
|
|
244
|
+
if (fs.existsSync(this.locksFile)) {
|
|
245
|
+
return JSON.parse(fs.readFileSync(this.locksFile, 'utf8'));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch { /* ignore */ }
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
saveLocks(locks) {
|
|
252
|
+
fs.writeFileSync(this.locksFile, JSON.stringify(locks, null, 2));
|
|
253
|
+
}
|
|
254
|
+
}
|