@flame0510/project-aether 1.1.15 → 1.2.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/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/components/Skeleton.tsx +132 -0
- package/app/credentials/PageClient.tsx +460 -139
- package/app/credentials/loading.tsx +19 -5
- package/docs/ARCHITECTURE.md +78 -31
- package/docs/FRONTEND-ARCHITECTURE.md +13 -5
- package/docs/REV4A.md +23 -14
- package/docs/dev/API-REFERENCE.md +356 -184
- package/docs/dev/DATABASE.md +8 -3
- package/docs/dev/GATEWAY.md +9 -7
- package/docs/dev/PROVIDERS.md +22 -35
- package/docs/rag/GLOSSARY.md +8 -8
- package/docs/rag/REV4A-OVERVIEW.md +7 -10
- package/docs/rag/WHAT-I-CAN-ANSWER.md +6 -6
- 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/package.json +1 -1
package/lib/docker-exec.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* thread does nothing else — no other request is served, no agent's stream
|
|
7
7
|
* advances. Node's own guidance names `execSync` in a server as a
|
|
8
8
|
* denial-of-service vector, and the numbers here bear that out: a bare
|
|
9
|
-
* `docker exec … echo` costs ~
|
|
9
|
+
* `docker exec … echo` costs ~90ms warm and ~225ms on the first call after an
|
|
10
|
+
* idle period, and the OpenClaw CLI inside a container
|
|
10
11
|
* takes 2.5–6s per invocation because it boots a whole Node process.
|
|
11
12
|
*
|
|
12
13
|
* Routes that walk every agent multiplied that: `/api/crons` measured 19.2s on a
|
|
@@ -30,7 +31,12 @@ const DEFAULT_MAX_BUFFER = 1024 * 1024;
|
|
|
30
31
|
|
|
31
32
|
export interface DockerExecOptions {
|
|
32
33
|
timeoutMs?: number;
|
|
33
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Extra environment for the command. Only the *names* reach argv, as `-e KEY`;
|
|
36
|
+
* docker then forwards each value from its own environment. A value passed as
|
|
37
|
+
* `-e KEY=VALUE` would sit in the process table for anyone on the host to read
|
|
38
|
+
* with `ps`, and in the text of any rejection Node builds from that argv.
|
|
39
|
+
*/
|
|
34
40
|
env?: Record<string, string>;
|
|
35
41
|
maxBuffer?: number;
|
|
36
42
|
signal?: AbortSignal;
|
|
@@ -38,11 +44,32 @@ export interface DockerExecOptions {
|
|
|
38
44
|
|
|
39
45
|
function baseArgs(container: string, opts?: DockerExecOptions): string[] {
|
|
40
46
|
const args = ['exec'];
|
|
41
|
-
for (const
|
|
47
|
+
for (const k of Object.keys(opts?.env ?? {})) args.push('-e', k);
|
|
42
48
|
args.push(container);
|
|
43
49
|
return args;
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Rebuild a failure without the command that produced it.
|
|
54
|
+
*
|
|
55
|
+
* Node composes its own message as `Command failed: <argv…>\n<stderr>`, and the
|
|
56
|
+
* argv holds whatever the caller put in the script. For credential delivery that
|
|
57
|
+
* is the secret — base64 is encoding, not protection — and the message travels:
|
|
58
|
+
* `syncProfileToAgents` puts it in `SyncResult.error`, which the sync route
|
|
59
|
+
* returns to the browser. The most common failure of all, a revoked or expired
|
|
60
|
+
* token, is exactly the one that triggers it.
|
|
61
|
+
*
|
|
62
|
+
* `stderr` is kept because that is the part that actually explains the failure.
|
|
63
|
+
*/
|
|
64
|
+
function execFailure(e: unknown, container: string): Error {
|
|
65
|
+
const err = e as { stderr?: string; code?: number; killed?: boolean; name?: string };
|
|
66
|
+
// An abort is the caller's own doing and is matched on elsewhere by name.
|
|
67
|
+
if (err.name === 'AbortError') return e as Error;
|
|
68
|
+
const cause = err.killed ? 'timed out' : err.code !== undefined ? `exit ${err.code}` : 'failed';
|
|
69
|
+
const detail = (err.stderr ?? '').trim();
|
|
70
|
+
return new Error(`docker exec ${container}: ${cause}${detail ? ` — ${detail}` : ''}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
/**
|
|
47
74
|
* Run a command inside a container, passing argv directly.
|
|
48
75
|
*
|
|
@@ -59,17 +86,22 @@ export async function dockerExec(
|
|
|
59
86
|
): Promise<string> {
|
|
60
87
|
if (!resolveDockerSocket()) throw new Error('Docker is not available');
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
try {
|
|
90
|
+
const { stdout } = await execFileAsync(
|
|
91
|
+
'docker',
|
|
92
|
+
[...baseArgs(container, opts), ...argv],
|
|
93
|
+
{
|
|
94
|
+
encoding: 'utf-8',
|
|
95
|
+
timeout: opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
96
|
+
maxBuffer: opts?.maxBuffer ?? DEFAULT_MAX_BUFFER,
|
|
97
|
+
signal: opts?.signal,
|
|
98
|
+
...(opts?.env ? { env: { ...process.env, ...opts.env } } : {}),
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
return stdout;
|
|
102
|
+
} catch (e) {
|
|
103
|
+
throw execFailure(e, container);
|
|
104
|
+
}
|
|
73
105
|
}
|
|
74
106
|
|
|
75
107
|
/**
|
|
@@ -132,6 +164,10 @@ export function dockerExecWithInput(
|
|
|
132
164
|
return new Promise((resolve, reject) => {
|
|
133
165
|
const child = spawn('docker', ['exec', '-i', ...baseArgs(container, opts).slice(1), ...argv], {
|
|
134
166
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
167
|
+
// `baseArgs` names the variables but carries no values, so they have to
|
|
168
|
+
// reach docker through its own environment — without this the container
|
|
169
|
+
// would be handed empty variables and never say why.
|
|
170
|
+
...(opts?.env ? { env: { ...process.env, ...opts.env } } : {}),
|
|
135
171
|
});
|
|
136
172
|
|
|
137
173
|
let stdout = '';
|