@aiwg/cli 2026.10.7 → 2026.10.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/src/artifacts/cli.js +9 -5
- package/dist/src/auth/config.js +3 -8
- package/dist/src/auth/resource-credentials.js +35 -2
- package/dist/src/cli/handlers/cost-report.js +7 -1
- package/dist/src/cli/handlers/decision.js +33 -8
- package/dist/src/cli/handlers/diagnose.js +13 -0
- package/dist/src/cli/handlers/local-executor.js +33 -6
- package/dist/src/cli/handlers/resource-versions.js +8 -5
- package/dist/src/cli/handlers/script-runner.js +13 -1
- package/dist/src/cli/handlers/use.js +18 -3
- package/dist/src/cost/fleet-report.js +20 -22
- package/dist/src/decision/driver.js +2 -2
- package/dist/src/decision/jev-credentials.js +18 -5
- package/dist/src/decision/ledger.js +230 -34
- package/dist/src/mcp/cli.mjs +105 -19
- package/dist/src/mcp/credentials.d.mts +4 -0
- package/dist/src/mcp/credentials.mjs +44 -3
- package/dist/src/mcp/grok-build-config.mjs +2 -0
- package/dist/src/mcp/omp-config.mjs +2 -0
- package/dist/src/mcp/registry.js +19 -15
- package/dist/src/mcp/registry.mjs +2 -18
- package/dist/src/mcp/tools/agentic-sandbox.mjs +46 -28
- package/dist/src/models/grok-build-models.js +20 -0
- package/dist/src/providers/provider-definitions.js +2 -2
- package/dist/src/resources/web-release.js +76 -65
- package/dist/src/serve/shared-host-scheduler.js +4 -1
- package/dist/src/storage/backends/fortemi.js +112 -50
- package/dist/src/storage/backends/logseq.js +16 -3
- package/dist/src/storage/backends/postgres.js +74 -33
- package/dist/src/storage/backends/postgrest.js +37 -13
- package/dist/src/storage/config.js +6 -1
- package/dist/src/uhp/config.js +4 -0
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveSensitiveInput } from '../../auth/sensitive-input.js';
|
|
2
|
+
import { credentialReferenceInput } from '../../mcp/credentials.mjs';
|
|
1
3
|
import { STORAGE_BACKEND_CONTRACT } from '../backend-contract.js';
|
|
2
4
|
import { postgresBatchId, postgresPayloadDigest } from './postgres.js';
|
|
3
5
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
@@ -18,6 +20,7 @@ export class PostgrestBackendError extends Error {
|
|
|
18
20
|
/** PostgreSQL accessMode=postgrest; not a separate storage engine. */
|
|
19
21
|
export class PostgrestStorageBackend {
|
|
20
22
|
options;
|
|
23
|
+
sensitiveInput;
|
|
21
24
|
descriptor = {
|
|
22
25
|
contract: STORAGE_BACKEND_CONTRACT,
|
|
23
26
|
backend: 'postgres-postgrest',
|
|
@@ -42,8 +45,9 @@ export class PostgrestStorageBackend {
|
|
|
42
45
|
maxResponseBytes;
|
|
43
46
|
maxBatchSize;
|
|
44
47
|
maxPageSize;
|
|
45
|
-
constructor(options) {
|
|
48
|
+
constructor(options, sensitiveInput = {}) {
|
|
46
49
|
this.options = options;
|
|
50
|
+
this.sensitiveInput = sensitiveInput;
|
|
47
51
|
this.baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
48
52
|
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
49
53
|
if (!this.fetchImpl)
|
|
@@ -185,7 +189,7 @@ export class PostgrestStorageBackend {
|
|
|
185
189
|
headers['Content-Type'] = options.contentType ?? 'application/json';
|
|
186
190
|
if (options.prefer)
|
|
187
191
|
headers.Prefer = options.prefer;
|
|
188
|
-
const authorization = this.authorization();
|
|
192
|
+
const authorization = await this.authorization();
|
|
189
193
|
if (authorization)
|
|
190
194
|
headers.Authorization = authorization;
|
|
191
195
|
let response;
|
|
@@ -195,8 +199,8 @@ export class PostgrestStorageBackend {
|
|
|
195
199
|
signal: AbortSignal.timeout(this.timeoutMs),
|
|
196
200
|
});
|
|
197
201
|
}
|
|
198
|
-
catch
|
|
199
|
-
throw new PostgrestBackendError('AIWG_POSTGREST_TRANSPORT_FAILED',
|
|
202
|
+
catch {
|
|
203
|
+
throw new PostgrestBackendError('AIWG_POSTGREST_TRANSPORT_FAILED', 'PostgREST transport failed; check connectivity and authorization.', true);
|
|
200
204
|
}
|
|
201
205
|
if (response.status === 204)
|
|
202
206
|
return undefined;
|
|
@@ -204,15 +208,23 @@ export class PostgrestStorageBackend {
|
|
|
204
208
|
if (declaredLength > this.maxResponseBytes) {
|
|
205
209
|
throw new PostgrestBackendError('AIWG_POSTGREST_RESPONSE_TOO_LARGE', `response exceeds ${this.maxResponseBytes} byte ceiling`);
|
|
206
210
|
}
|
|
207
|
-
|
|
211
|
+
let responseBody;
|
|
212
|
+
try {
|
|
213
|
+
responseBody = await response.text();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
throw new PostgrestBackendError('AIWG_POSTGREST_TRANSPORT_FAILED', 'PostgREST response could not be read.', true);
|
|
217
|
+
}
|
|
208
218
|
if (Buffer.byteLength(responseBody) > this.maxResponseBytes) {
|
|
209
219
|
throw new PostgrestBackendError('AIWG_POSTGREST_RESPONSE_TOO_LARGE', `response exceeds ${this.maxResponseBytes} byte ceiling`);
|
|
210
220
|
}
|
|
211
221
|
if (!response.ok) {
|
|
212
|
-
const
|
|
222
|
+
const secrets = authorization
|
|
223
|
+
? [authorization, authorization.trim(), authorization.trim().replace(/^\S+\s+/, '')].filter(Boolean) : [];
|
|
224
|
+
const databaseError = parseDatabaseError(responseBody, secrets);
|
|
213
225
|
const retryable = response.status === 408 || response.status === 425 || response.status === 429 ||
|
|
214
226
|
response.status >= 500 || isRetryableDatabaseCode(databaseError.databaseCode);
|
|
215
|
-
const exposedCode = databaseError.message
|
|
227
|
+
const exposedCode = /^AIWG_[A-Z0-9_]+$/.test(databaseError.message ?? '')
|
|
216
228
|
? databaseError.message
|
|
217
229
|
: retryable ? 'AIWG_POSTGREST_RETRYABLE' : 'AIWG_POSTGREST_REQUEST_FAILED';
|
|
218
230
|
throw new PostgrestBackendError(exposedCode, `PostgREST request failed with HTTP ${response.status}${databaseError.databaseCode ? ` (database ${databaseError.databaseCode})` : ''}`, retryable, parseRetryAfter(response.headers.get('retry-after')));
|
|
@@ -224,12 +236,19 @@ export class PostgrestStorageBackend {
|
|
|
224
236
|
throw new PostgrestBackendError('AIWG_POSTGREST_RESPONSE_INVALID', 'PostgREST returned invalid JSON');
|
|
225
237
|
}
|
|
226
238
|
}
|
|
227
|
-
authorization() {
|
|
239
|
+
async authorization() {
|
|
228
240
|
if (!this.options.authorizationEnv)
|
|
229
241
|
return undefined;
|
|
230
|
-
const
|
|
242
|
+
const input = credentialReferenceInput(this.options.authorizationEnv, 'PostgREST authorizationEnv');
|
|
243
|
+
let value;
|
|
244
|
+
try {
|
|
245
|
+
value = input.env ? (this.sensitiveInput.environment ?? process.env)[input.env] : await resolveSensitiveInput(input, this.sensitiveInput);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
throw new PostgrestBackendError('AIWG_POSTGREST_CREDENTIAL_UNAVAILABLE', error.message);
|
|
249
|
+
}
|
|
231
250
|
if (!value)
|
|
232
|
-
throw new PostgrestBackendError('AIWG_POSTGREST_CREDENTIAL_UNAVAILABLE',
|
|
251
|
+
throw new PostgrestBackendError('AIWG_POSTGREST_CREDENTIAL_UNAVAILABLE', 'PostgREST authorizationEnv is not set; use ENV_NAME, file:ABSOLUTE_PATH or store:NAME.');
|
|
233
252
|
return value;
|
|
234
253
|
}
|
|
235
254
|
assertMutationIdentity(mutation) {
|
|
@@ -294,12 +313,17 @@ function parseCsv(input) {
|
|
|
294
313
|
}
|
|
295
314
|
return { header, rows: output };
|
|
296
315
|
}
|
|
297
|
-
function parseDatabaseError(body) {
|
|
316
|
+
function parseDatabaseError(body, secrets) {
|
|
298
317
|
try {
|
|
299
318
|
const parsed = JSON.parse(body);
|
|
319
|
+
// Decode JSON escapes before redaction: encoded echoes must not become codes.
|
|
320
|
+
const redact = (value) => typeof value === 'string'
|
|
321
|
+
? secrets.reduce((text, secret) => text.split(secret).join('[REDACTED]'), value) : undefined;
|
|
322
|
+
const databaseCode = redact(parsed.code);
|
|
323
|
+
const message = redact(parsed.message);
|
|
300
324
|
return {
|
|
301
|
-
...(
|
|
302
|
-
...(
|
|
325
|
+
...(databaseCode && /^[A-Z0-9]{5}$/.test(databaseCode) ? { databaseCode } : {}),
|
|
326
|
+
...(message ? { message } : {}),
|
|
303
327
|
};
|
|
304
328
|
}
|
|
305
329
|
catch {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @issue #953
|
|
15
15
|
*/
|
|
16
16
|
import { existsSync } from 'fs';
|
|
17
|
+
import { credentialReferenceInput } from '../mcp/credentials.mjs';
|
|
17
18
|
import { readFile } from 'fs/promises';
|
|
18
19
|
import { resolve, isAbsolute, join } from 'path';
|
|
19
20
|
import { homedir } from 'os';
|
|
@@ -171,8 +172,12 @@ function validateBackendConfig(raw, source) {
|
|
|
171
172
|
requireString(obj, 'vault', source);
|
|
172
173
|
return obj;
|
|
173
174
|
case 'logseq':
|
|
174
|
-
rejectUnknownKeys(obj, ['type', 'graph', 'apiUrl', 'useApi'], source, 'backend');
|
|
175
|
+
rejectUnknownKeys(obj, ['type', 'graph', 'apiUrl', 'apiTokenEnv', 'useApi'], source, 'backend');
|
|
175
176
|
requireString(obj, 'graph', source);
|
|
177
|
+
if (obj['apiTokenEnv'] !== undefined) {
|
|
178
|
+
requireString(obj, 'apiTokenEnv', source);
|
|
179
|
+
credentialReferenceInput(obj['apiTokenEnv'], 'Logseq apiTokenEnv');
|
|
180
|
+
}
|
|
176
181
|
return obj;
|
|
177
182
|
case 'notion': {
|
|
178
183
|
rejectUnknownKeys(obj, ['type', 'parent', 'externalIdProperty'], source, 'backend');
|
package/dist/src/uhp/config.js
CHANGED
|
@@ -100,6 +100,10 @@ export function validateUhpConfig(config) {
|
|
|
100
100
|
if (!profile.credential || profile.credential.source !== 'env' || !/^[A-Z_][A-Z0-9_]*$/.test(profile.credential.name ?? '')) {
|
|
101
101
|
errors.push(`${where}.credential: must be an env secret reference with an uppercase variable name`);
|
|
102
102
|
}
|
|
103
|
+
if (profile.credential && typeof profile.credential === 'object'
|
|
104
|
+
&& Object.keys(profile.credential).some(key => key !== 'source' && key !== 'name')) {
|
|
105
|
+
errors.push(`${where}.credential: must contain only source and name; inline credentials are forbidden`);
|
|
106
|
+
}
|
|
103
107
|
if (profile.defaultHarness !== undefined && !OBJECT_ID.test(profile.defaultHarness))
|
|
104
108
|
errors.push(`${where}.defaultHarness: malformed UHP harness id`);
|
|
105
109
|
if (profile.defaultModel !== undefined && !MODEL_ID.test(profile.defaultModel))
|