@foxden-app/foxclaw 0.4.0 → 0.4.1
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/auth/mirror.d.ts +11 -0
- package/dist/auth/mirror.js +28 -0
- package/dist/main.js +21 -0
- package/package.json +1 -1
package/dist/auth/mirror.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface AuthMirrorRuntime {
|
|
|
4
4
|
label?: string;
|
|
5
5
|
authDir: string;
|
|
6
6
|
notify?: (message: string) => Promise<void>;
|
|
7
|
+
validate?: (context: AuthMirrorValidationContext) => Promise<AuthMirrorValidationResult | boolean>;
|
|
7
8
|
}
|
|
8
9
|
export interface AuthMirrorStatus {
|
|
9
10
|
candidateName: string;
|
|
@@ -11,6 +12,15 @@ export interface AuthMirrorStatus {
|
|
|
11
12
|
sourceLabel: string;
|
|
12
13
|
syncedAt: string;
|
|
13
14
|
}
|
|
15
|
+
export interface AuthMirrorValidationContext {
|
|
16
|
+
candidateName: string;
|
|
17
|
+
accountId: string;
|
|
18
|
+
lastRefreshMs: number;
|
|
19
|
+
}
|
|
20
|
+
export interface AuthMirrorValidationResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
reason?: string | null;
|
|
23
|
+
}
|
|
14
24
|
export declare class AuthCandidateMirror {
|
|
15
25
|
private readonly canonicalDir;
|
|
16
26
|
private readonly runtimes;
|
|
@@ -29,6 +39,7 @@ export declare class AuthCandidateMirror {
|
|
|
29
39
|
syncRuntimeCandidate(runtimeId: string, candidateName: string): Promise<boolean>;
|
|
30
40
|
private scan;
|
|
31
41
|
private propagateValidatedCandidate;
|
|
42
|
+
private validateRuntimeCandidate;
|
|
32
43
|
private withActivity;
|
|
33
44
|
private ensureCanonicalDefaultCandidate;
|
|
34
45
|
private collectCandidateNames;
|
package/dist/auth/mirror.js
CHANGED
|
@@ -96,6 +96,15 @@ export class AuthCandidateMirror {
|
|
|
96
96
|
if (record.lastRefreshMs <= previousRefresh) {
|
|
97
97
|
return false;
|
|
98
98
|
}
|
|
99
|
+
const validation = await this.validateRuntimeCandidate(runtime, name, record);
|
|
100
|
+
if (!validation.ok) {
|
|
101
|
+
this.logger.warn('auth.mirror.validation_failed', {
|
|
102
|
+
runtimeId: runtime.id,
|
|
103
|
+
name,
|
|
104
|
+
reason: validation.reason ?? 'unknown',
|
|
105
|
+
});
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
99
108
|
await atomicWrite(canonicalPath, record.raw);
|
|
100
109
|
for (const target of this.runtimes) {
|
|
101
110
|
if (target.id !== runtime.id) {
|
|
@@ -116,6 +125,25 @@ export class AuthCandidateMirror {
|
|
|
116
125
|
await Promise.allSettled(this.runtimes.map((target) => target.notify?.(message)));
|
|
117
126
|
return true;
|
|
118
127
|
}
|
|
128
|
+
async validateRuntimeCandidate(runtime, name, record) {
|
|
129
|
+
if (!runtime.validate) {
|
|
130
|
+
return { ok: true };
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const result = await runtime.validate({
|
|
134
|
+
candidateName: name,
|
|
135
|
+
accountId: record.accountId,
|
|
136
|
+
lastRefreshMs: record.lastRefreshMs,
|
|
137
|
+
});
|
|
138
|
+
if (typeof result === 'boolean') {
|
|
139
|
+
return { ok: result };
|
|
140
|
+
}
|
|
141
|
+
return { ok: Boolean(result.ok), reason: result.reason ?? null };
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return { ok: false, reason: formatError(error) };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
119
147
|
async withActivity(operation) {
|
|
120
148
|
this.activeOperations += 1;
|
|
121
149
|
try {
|
package/dist/main.js
CHANGED
|
@@ -197,6 +197,7 @@ async function runServeCli() {
|
|
|
197
197
|
id: runtime.id,
|
|
198
198
|
label: runtime.bot.username ? `@${runtime.bot.username}` : runtime.id,
|
|
199
199
|
authDir: runtime.home,
|
|
200
|
+
validate: async (context) => validateRefreshedAuthCandidate(runtime, context.candidateName),
|
|
200
201
|
notify: async (message) => {
|
|
201
202
|
const chatId = store.getTelegramPrivateChatId(runtime.id);
|
|
202
203
|
if (chatId) {
|
|
@@ -412,6 +413,26 @@ async function runServeCli() {
|
|
|
412
413
|
throw error;
|
|
413
414
|
}
|
|
414
415
|
}
|
|
416
|
+
async function validateRefreshedAuthCandidate(runtime, candidateName) {
|
|
417
|
+
const authPath = path.join(runtime.home, 'auth.json');
|
|
418
|
+
const candidatePath = path.join(runtime.home, candidateName);
|
|
419
|
+
const [currentTarget, candidateTarget] = await Promise.all([
|
|
420
|
+
fs.promises.realpath(authPath).catch(() => null),
|
|
421
|
+
fs.promises.realpath(candidatePath).catch(() => null),
|
|
422
|
+
]);
|
|
423
|
+
if (!currentTarget || !candidateTarget || currentTarget !== candidateTarget) {
|
|
424
|
+
return { ok: false, reason: 'candidate is not the current auth target' };
|
|
425
|
+
}
|
|
426
|
+
if (!runtime.app.isConnected()) {
|
|
427
|
+
return { ok: false, reason: 'source app-server is not connected' };
|
|
428
|
+
}
|
|
429
|
+
const account = await runtime.app.readAccount();
|
|
430
|
+
if (!account || account.type !== 'chatgpt' || account.requiresOpenaiAuth) {
|
|
431
|
+
return { ok: false, reason: 'source app-server did not report an active ChatGPT account' };
|
|
432
|
+
}
|
|
433
|
+
await runtime.app.readAccountRateLimits();
|
|
434
|
+
return { ok: true };
|
|
435
|
+
}
|
|
415
436
|
async function initConfig() {
|
|
416
437
|
const envPath = process.env.FOXCLAW_ENV?.trim() || DEFAULT_ENV_PATH;
|
|
417
438
|
fs.mkdirSync(path.dirname(envPath), { recursive: true });
|