@flame0510/project-aether 1.1.15 → 1.3.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 +2 -1
- package/app/agents/ModelSection.tsx +313 -0
- package/app/agents/PageClient.tsx +83 -4
- package/app/agents/create/page.tsx +8 -21
- package/app/api/agents/[id]/model/route.ts +113 -0
- package/app/api/agents/[id]/recreate/route.ts +10 -34
- package/app/api/agents/[id]/route.ts +10 -29
- package/app/api/agents/create/route.ts +59 -57
- package/app/api/agents/models-summary/route.ts +163 -0
- package/app/api/assistant/route.ts +36 -15
- package/app/api/credentials/[id]/sync/route.ts +3 -3
- package/app/api/credentials/detect/route.ts +126 -176
- package/app/api/credentials/route.ts +3 -0
- package/app/api/gateway/agent/route.ts +23 -6
- package/app/api/gateway/provider/keys.ts +13 -1
- package/app/api/gateway/provider/route.ts +43 -12
- package/app/api/gateway/sync.ts +248 -72
- package/app/api/models/route.ts +28 -34
- package/app/api/provider/auth.ts +65 -0
- package/app/api/provider/upstream.ts +9 -2
- package/app/api/provider/v1/chat/completions/route.ts +22 -16
- package/app/api/provider/v1/models/route.ts +26 -133
- package/app/components/PulseChat.tsx +25 -39
- package/app/components/Skeleton.tsx +132 -0
- package/app/components/ui/RemoveButton.tsx +46 -0
- package/app/components/ui/Select.tsx +3 -2
- package/app/components/ui/index.ts +1 -0
- package/app/credentials/PageClient.tsx +461 -140
- package/app/credentials/loading.tsx +19 -5
- package/app/gateway/PageClient.tsx +257 -673
- package/app/globals.css +8 -0
- package/app/lib/models-context.tsx +43 -7
- package/app/wizard/useWizard.ts +6 -1
- package/bin/rev4a.js +73 -9
- package/docs/ARCHITECTURE.md +92 -33
- package/docs/FRONTEND-ARCHITECTURE.md +36 -6
- package/docs/REV4A.md +62 -30
- package/docs/dev/API-REFERENCE.md +490 -227
- package/docs/dev/DATABASE.md +8 -3
- package/docs/dev/GATEWAY.md +236 -92
- package/docs/dev/PROVIDERS.md +44 -44
- package/docs/rag/DATA-FRESHNESS.md +57 -28
- package/docs/rag/GLOSSARY.md +20 -18
- package/docs/rag/REV4A-OVERVIEW.md +28 -32
- package/docs/rag/WHAT-I-CAN-ANSWER.md +10 -12
- package/instrumentation.ts +9 -1
- package/lib/agent-readiness.ts +110 -0
- package/lib/channelManager.ts +64 -22
- package/lib/container-file.ts +27 -0
- package/lib/credentials/delivery.ts +212 -119
- package/lib/credentials/detect.ts +229 -97
- package/lib/credentials/providers.ts +38 -7
- package/lib/credentials/vault.ts +78 -13
- package/lib/docker-exec.ts +50 -14
- package/lib/model-catalogue.ts +140 -27
- package/lib/rev4a-paths.ts +0 -21
- package/model-pricing.json +118 -110
- package/models.config.json +27 -12
- package/package.json +1 -1
- package/app/api/gateway/route.ts +0 -191
package/lib/channelManager.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* - gateway/config-channels.md
|
|
22
22
|
* - concepts/multi-agent.md (bindings)
|
|
23
23
|
*/
|
|
24
|
-
import { execSync } from 'child_process';
|
|
24
|
+
import { execSync, execFileSync } from 'child_process';
|
|
25
25
|
|
|
26
26
|
// ── Types ────────────────────────────────────
|
|
27
27
|
|
|
@@ -232,7 +232,7 @@ export function removeTelegram(agentId: string): { success: boolean; error?: str
|
|
|
232
232
|
* The agentId in the binding MUST be "main" (OpenClaw's default agent)
|
|
233
233
|
* so that sessions are created as `agent:main:main`.
|
|
234
234
|
* Using the container name as agentId would create sessions like
|
|
235
|
-
* `agent
|
|
235
|
+
* `agent:<AGENT_NAME>:main` which breaks model bindings that target
|
|
236
236
|
* `agent:main:*`.
|
|
237
237
|
*
|
|
238
238
|
* Per multi-agent.md: bindings route (channel, accountId) to agentId.
|
|
@@ -360,33 +360,75 @@ export function approvePairing(agentId: string, code: string, channel: string =
|
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
/**
|
|
363
|
+
/**
|
|
364
|
+
* Script run inside the container to drop one sender from the allowlist file.
|
|
365
|
+
*
|
|
366
|
+
* The sender id arrives as `process.argv[1]`, never interpolated into the source.
|
|
367
|
+
* The previous version built this string by substitution, applying shell-style
|
|
368
|
+
* escaping to a value that lands inside a JavaScript string literal: an id
|
|
369
|
+
* containing an apostrophe closed the literal and the rest ran as code.
|
|
370
|
+
*
|
|
371
|
+
* It prints one of three sentinels so the caller can tell the outcomes apart —
|
|
372
|
+
* `docker exec` exits 0 even when killed by the timeout, so an empty result
|
|
373
|
+
* cannot be read as success (see docs/ARCHITECTURE.md §3.1).
|
|
374
|
+
*/
|
|
375
|
+
const REVOKE_SCRIPT = `
|
|
376
|
+
const fs = require('fs');
|
|
377
|
+
const [p, target] = [process.argv[1], process.argv[2]];
|
|
378
|
+
try {
|
|
379
|
+
if (!fs.existsSync(p)) { console.log('revoke:no-store'); process.exit(0); }
|
|
380
|
+
const d = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
381
|
+
const before = (d.allowFrom || []).length;
|
|
382
|
+
d.allowFrom = (d.allowFrom || []).filter((e) => String(e) !== target);
|
|
383
|
+
if (d.allowFrom.length === before) { console.log('revoke:absent'); process.exit(0); }
|
|
384
|
+
fs.writeFileSync(p, JSON.stringify(d, null, 2), 'utf8');
|
|
385
|
+
console.log('revoke:ok');
|
|
386
|
+
} catch (e) {
|
|
387
|
+
console.log('revoke:error ' + e.message);
|
|
388
|
+
}
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Revoke an approved sender from the allowlist.
|
|
393
|
+
*
|
|
394
|
+
* There is no CLI for this. `openclaw pairing` exposes only `approve`, `list` and
|
|
395
|
+
* `help` — verified on both 2026.7.1-2 and 2026.9.3 — so the allowlist store has
|
|
396
|
+
* to be edited directly.
|
|
397
|
+
*
|
|
398
|
+
* The previous implementation tried `openclaw pairing revoke` first and treated
|
|
399
|
+
* any output that lacked "Unknown command" or "not found" as success. OpenClaw
|
|
400
|
+
* actually answers `OpenClaw does not know the command "revoke".`, which contains
|
|
401
|
+
* neither, so every revocation reported success, changed nothing, and never
|
|
402
|
+
* reached the fallback.
|
|
403
|
+
*
|
|
404
|
+
* On 2026.9.x this store moved into SQLite and the file is gone. Rather than
|
|
405
|
+
* silently doing nothing again, that case now fails loudly. Revocation against 9.x's SQLite store is not
|
|
406
|
+
* implemented yet.
|
|
407
|
+
*/
|
|
364
408
|
export function revokePairing(agentId: string, senderId: string, channel: string = 'telegram'): { success: boolean; error?: string } {
|
|
365
409
|
const container = findContainerById(agentId);
|
|
366
410
|
if (!container) return { success: false, error: `Agent container not found: ${agentId}` };
|
|
367
411
|
|
|
368
412
|
try {
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return { success: true };
|
|
377
|
-
}
|
|
413
|
+
// execFileSync, not a shell: argv is passed through untouched, so nothing in
|
|
414
|
+
// `senderId` can be read as syntax.
|
|
415
|
+
const out = execFileSync(
|
|
416
|
+
'docker',
|
|
417
|
+
['exec', container, 'node', '-e', REVOKE_SCRIPT, pairingAllowFile(channel), senderId],
|
|
418
|
+
{ encoding: 'utf-8', timeout: 15000, maxBuffer: 1024 * 1024 },
|
|
419
|
+
).trim();
|
|
378
420
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
421
|
+
if (out.includes('revoke:ok')) return { success: true };
|
|
422
|
+
if (out.includes('revoke:absent')) return { success: true }; // already gone — the desired state
|
|
423
|
+
if (out.includes('revoke:no-store')) {
|
|
424
|
+
return {
|
|
425
|
+
success: false,
|
|
426
|
+
error: 'No allowlist file in this container: nobody has been approved on this '
|
|
427
|
+
+ 'channel yet, or the agent runs OpenClaw 9.x, whose pairing store is in '
|
|
428
|
+
+ 'SQLite and cannot be revoked from here yet.',
|
|
429
|
+
};
|
|
387
430
|
}
|
|
388
|
-
|
|
389
|
-
return { success: true };
|
|
431
|
+
return { success: false, error: out || 'revoke produced no output' };
|
|
390
432
|
} catch (e) {
|
|
391
433
|
return { success: false, error: (e as Error).message };
|
|
392
434
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading one file out of an agent container, running or not.
|
|
3
|
+
*
|
|
4
|
+
* `docker exec` cannot reach a stopped or paused container; `docker cp` can, and
|
|
5
|
+
* streams the file as a tar archive. Used by the config sync, which also writes
|
|
6
|
+
* back through `docker cp`, and by the model summary behind the agent cards.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** The single regular file in a `docker cp … -` tar stream: its bytes and mode. */
|
|
10
|
+
export function singleFileFromTar(tar: Buffer): { body: Buffer; mode: number } {
|
|
11
|
+
if (tar.length < 512) throw new Error('docker cp returned an empty archive');
|
|
12
|
+
const header = tar.subarray(0, 512);
|
|
13
|
+
const field = (start: number, len: number) => {
|
|
14
|
+
const text = header.subarray(start, start + len).toString('ascii');
|
|
15
|
+
const nul = text.indexOf('\0');
|
|
16
|
+
return (nul === -1 ? text : text.slice(0, nul)).trim();
|
|
17
|
+
};
|
|
18
|
+
const type = field(156, 1);
|
|
19
|
+
// '0' (or NUL on old writers) is a regular file. Anything else — a pax header,
|
|
20
|
+
// a directory — is not the shape expected, and guessing past it could mean
|
|
21
|
+
// writing a config that was never read.
|
|
22
|
+
if (type !== '0' && type !== '') throw new Error(`docker cp returned a non-file entry (type ${type})`);
|
|
23
|
+
const size = parseInt(field(124, 12), 8);
|
|
24
|
+
if (!Number.isFinite(size) || tar.length < 512 + size) throw new Error('docker cp returned a truncated archive');
|
|
25
|
+
const mode = parseInt(field(100, 8), 8);
|
|
26
|
+
return { body: tar.subarray(512, 512 + size), mode: Number.isFinite(mode) ? mode & 0o777 : 0o600 };
|
|
27
|
+
}
|