clawsocial-plugin 1.6.6 → 1.6.8

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/api.ts +1 -0
  3. package/src/store.ts +98 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawsocial-plugin",
3
- "version": "1.6.6",
3
+ "version": "1.6.8",
4
4
  "description": "ClawSocial OpenClaw Plugin — social discovery for AI agents",
5
5
  "type": "module",
6
6
  "author": "ClawSocial",
package/src/api.ts CHANGED
@@ -35,6 +35,7 @@ async function doRequest<T = unknown>(
35
35
  method,
36
36
  headers: {
37
37
  "Content-Type": "application/json",
38
+ "X-App-Name": "clawsocial-plugin",
38
39
  ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
39
40
  },
40
41
  body: body ? JSON.stringify(body) : undefined,
package/src/store.ts CHANGED
@@ -79,12 +79,26 @@ function settingsFile(): string {
79
79
  }
80
80
 
81
81
  export function getSettings(): Settings {
82
- return { ...DEFAULT_SETTINGS, ...readJSON<Partial<Settings>>(settingsFile(), {}) };
82
+ const s = readJSON<Partial<Settings>>(settingsFile(), {});
83
+ if (Object.keys(s).length === 0) {
84
+ const agentId = getState().agent_id;
85
+ if (agentId) {
86
+ const backup = backupRead<Partial<Settings>>(agentId, "settings.json", {});
87
+ if (Object.keys(backup).length > 0) {
88
+ writeJSON(settingsFile(), backup);
89
+ return { ...DEFAULT_SETTINGS, ...backup };
90
+ }
91
+ }
92
+ }
93
+ return { ...DEFAULT_SETTINGS, ...s };
83
94
  }
84
95
 
85
96
  export function setSettings(data: Partial<Settings>): void {
86
97
  const s = getSettings();
87
- writeJSON(settingsFile(), { ...s, ...data });
98
+ const merged = { ...s, ...data };
99
+ writeJSON(settingsFile(), merged);
100
+ const agentId = readJSON<AgentState>(stateFile(), {}).agent_id;
101
+ if (agentId) backupWrite(agentId, "settings.json", merged);
88
102
  }
89
103
 
90
104
  // ── Agent state ─────────────────────────────────────────────────────
@@ -101,17 +115,34 @@ export type AgentState = {
101
115
  // ── Sessions ────────────────────────────────────────────────────────
102
116
 
103
117
  export function getSessions(): SessionsMap {
104
- return readJSON<SessionsMap>(sessionsFile(), {});
118
+ const sessions = readJSON<SessionsMap>(sessionsFile(), {});
119
+ if (Object.keys(sessions).length === 0) {
120
+ const agentId = getState().agent_id;
121
+ if (agentId) {
122
+ const backup = backupRead<SessionsMap>(agentId, "sessions.json", {});
123
+ if (Object.keys(backup).length > 0) {
124
+ writeJSON(sessionsFile(), backup);
125
+ return backup;
126
+ }
127
+ }
128
+ }
129
+ return sessions;
105
130
  }
106
131
 
107
132
  export function getSession(id: string): LocalSession | null {
108
133
  return getSessions()[id] ?? null;
109
134
  }
110
135
 
136
+ function writeSessions(sessions: SessionsMap): void {
137
+ writeJSON(sessionsFile(), sessions);
138
+ const agentId = readJSON<AgentState>(stateFile(), {}).agent_id;
139
+ if (agentId) backupWrite(agentId, "sessions.json", sessions);
140
+ }
141
+
111
142
  export function upsertSession(id: string, data: Partial<LocalSession>): LocalSession {
112
143
  const sessions = getSessions();
113
144
  sessions[id] = { ...(sessions[id] ?? { id, messages: [], unread: 0 }), ...data, id };
114
- writeJSON(sessionsFile(), sessions);
145
+ writeSessions(sessions);
115
146
  return sessions[id];
116
147
  }
117
148
 
@@ -127,59 +158,74 @@ export function addMessage(sessionId: string, msg: LocalMessage): void {
127
158
  sessions[sessionId].unread = (sessions[sessionId].unread ?? 0) + 1;
128
159
  }
129
160
  sessions[sessionId].updated_at = Math.floor(Date.now() / 1000);
130
- writeJSON(sessionsFile(), sessions);
161
+ writeSessions(sessions);
131
162
  }
132
163
 
133
164
  export function markRead(sessionId: string): void {
134
165
  const sessions = getSessions();
135
166
  if (sessions[sessionId]) {
136
167
  sessions[sessionId].unread = 0;
137
- writeJSON(sessionsFile(), sessions);
168
+ writeSessions(sessions);
138
169
  }
139
170
  }
140
171
 
141
- // ── Credential backup (survives plugin/OpenClaw reinstall) ──────────
172
+ // ── Backup (survives plugin/OpenClaw reinstall) ─────────────────────
173
+ // Backup layout:
174
+ // ~/.clawsocial/
175
+ // last_active ← agent_id of most recently used account
176
+ // <agent_id>/
177
+ // credentials.json
178
+ // sessions.json
179
+ // settings.json
180
+ // contacts.json
181
+
182
+ const BACKUP_ROOT = path.join(os.homedir(), ".clawsocial");
142
183
 
143
- function credentialBackupFile(): string {
144
- return path.join(os.homedir(), ".clawsocial", "credentials.json");
184
+ function backupDir(agentId: string): string {
185
+ return path.join(BACKUP_ROOT, agentId);
145
186
  }
146
187
 
147
- function backupCredentials(state: AgentState): void {
148
- if (!state.agent_id || !state.api_key) return;
188
+ function backupWrite(agentId: string, name: string, data: unknown): void {
149
189
  try {
150
- const dir = path.dirname(credentialBackupFile());
190
+ const dir = backupDir(agentId);
151
191
  fs.mkdirSync(dir, { recursive: true });
152
- writeJSON(credentialBackupFile(), {
153
- agent_id: state.agent_id,
154
- api_key: state.api_key,
155
- public_name: state.public_name,
156
- lang: state.lang,
157
- });
192
+ writeJSON(path.join(dir, name), data);
193
+ // Update last_active marker
194
+ fs.writeFileSync(path.join(BACKUP_ROOT, "last_active"), agentId);
158
195
  } catch {
159
196
  // best-effort backup, don't fail if write fails
160
197
  }
161
198
  }
162
199
 
163
- function restoreCredentials(): AgentState | null {
200
+ function backupRead<T>(agentId: string, name: string, fallback: T): T {
164
201
  try {
165
- const backup = readJSON<AgentState>(credentialBackupFile(), {});
166
- if (backup.agent_id && backup.api_key) return backup;
202
+ return readJSON<T>(path.join(backupDir(agentId), name), fallback);
167
203
  } catch {
168
- // no backup available
204
+ return fallback;
205
+ }
206
+ }
207
+
208
+ function getLastActiveAgentId(): string | null {
209
+ try {
210
+ return fs.readFileSync(path.join(BACKUP_ROOT, "last_active"), "utf8").trim() || null;
211
+ } catch {
212
+ return null;
169
213
  }
170
- return null;
171
214
  }
172
215
 
173
216
  // ── Agent state ─────────────────────────────────────────────────────
174
217
 
175
218
  export function getState(): AgentState {
176
219
  const state = readJSON<AgentState>(stateFile(), {});
177
- // If state is empty (e.g. after reinstall), try restoring from backup
178
220
  if (!state.agent_id || !state.api_key) {
179
- const backup = restoreCredentials();
180
- if (backup) {
181
- writeJSON(stateFile(), backup);
182
- return backup;
221
+ // Try restoring from backup using last_active agent
222
+ const lastId = getLastActiveAgentId();
223
+ if (lastId) {
224
+ const backup = backupRead<AgentState>(lastId, "credentials.json", {});
225
+ if (backup.agent_id && backup.api_key) {
226
+ writeJSON(stateFile(), backup);
227
+ return backup;
228
+ }
183
229
  }
184
230
  }
185
231
  return state;
@@ -189,8 +235,14 @@ export function setState(data: Partial<AgentState>): void {
189
235
  const s = getState();
190
236
  const merged = { ...s, ...data };
191
237
  writeJSON(stateFile(), merged);
192
- // Backup credentials whenever they change
193
- backupCredentials(merged);
238
+ if (merged.agent_id && merged.api_key) {
239
+ backupWrite(merged.agent_id, "credentials.json", {
240
+ agent_id: merged.agent_id,
241
+ api_key: merged.api_key,
242
+ public_name: merged.public_name,
243
+ lang: merged.lang,
244
+ });
245
+ }
194
246
  }
195
247
 
196
248
  // ── Contacts ─────────────────────────────────────────────────────────
@@ -211,10 +263,20 @@ function contactsFile(): string {
211
263
  export function readContacts(): Contact[] {
212
264
  try {
213
265
  const data = JSON.parse(fs.readFileSync(contactsFile(), "utf8"));
214
- return Array.isArray(data?.contacts) ? data.contacts : [];
266
+ if (Array.isArray(data?.contacts) && data.contacts.length > 0) return data.contacts;
215
267
  } catch {
216
- return [];
268
+ // fall through to backup
269
+ }
270
+ const agentId = getState().agent_id;
271
+ if (agentId) {
272
+ const backup = backupRead<{ contacts?: Contact[] }>(agentId, "contacts.json", {});
273
+ if (Array.isArray(backup?.contacts) && backup.contacts.length > 0) {
274
+ fs.mkdirSync(path.dirname(contactsFile()), { recursive: true });
275
+ fs.writeFileSync(contactsFile(), JSON.stringify({ contacts: backup.contacts }, null, 2));
276
+ return backup.contacts;
277
+ }
217
278
  }
279
+ return [];
218
280
  }
219
281
 
220
282
  export function upsertContact(contact: Omit<Contact, "added_at"> & { added_at?: number }): void {
@@ -226,7 +288,10 @@ export function upsertContact(contact: Omit<Contact, "added_at"> & { added_at?:
226
288
  } else {
227
289
  contacts.push(entry);
228
290
  }
229
- fs.writeFileSync(contactsFile(), JSON.stringify({ contacts }, null, 2));
291
+ const data = { contacts };
292
+ fs.writeFileSync(contactsFile(), JSON.stringify(data, null, 2));
293
+ const agentId = readJSON<AgentState>(stateFile(), {}).agent_id;
294
+ if (agentId) backupWrite(agentId, "contacts.json", data);
230
295
  }
231
296
 
232
297
  export function lookupContactByName(name: string): Contact[] {