@aiwg/cli 2026.10.8 → 2026.10.10

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 (104) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +197 -3
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/artifacts/cli.js +9 -5
  4. package/dist/src/auth/config.js +3 -8
  5. package/dist/src/auth/resource-credentials.js +35 -2
  6. package/dist/src/cli/handlers/diagnose.js +13 -0
  7. package/dist/src/cli/handlers/mc-admission.js +39 -0
  8. package/dist/src/cli/handlers/mc.js +2 -31
  9. package/dist/src/cli/handlers/models-help.js +52 -0
  10. package/dist/src/cli/handlers/models.js +11 -81
  11. package/dist/src/cli/handlers/resource-versions.js +8 -5
  12. package/dist/src/cli/handlers/script-runner.js +13 -1
  13. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  14. package/dist/src/cli/handlers/use.js +209 -233
  15. package/dist/src/cli/ui.js +1 -0
  16. package/dist/src/config/aiwg-config.js +38 -184
  17. package/dist/src/config/cli-help.js +54 -0
  18. package/dist/src/config/cli.js +5 -69
  19. package/dist/src/config/config-normalization.js +10 -0
  20. package/dist/src/config/worker-activation-gate.js +26 -0
  21. package/dist/src/config/worker-adoption-cli.js +102 -0
  22. package/dist/src/config/worker-adoption.js +368 -0
  23. package/dist/src/config/worker-config-digest.js +14 -0
  24. package/dist/src/config/worker-group-templates.js +150 -0
  25. package/dist/src/config/worker-guidance-warning.js +28 -0
  26. package/dist/src/config/worker-owner-lock.js +152 -0
  27. package/dist/src/config/worker-parallelism.js +154 -0
  28. package/dist/src/config/worker-write.js +75 -0
  29. package/dist/src/decision/telemetry/exporter.js +2 -1
  30. package/dist/src/decision/telemetry/index.js +1 -0
  31. package/dist/src/decision/telemetry/mapping.js +43 -0
  32. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  33. package/dist/src/decision/telemetry/redaction.js +6 -1
  34. package/dist/src/decision/telemetry/types.js +3 -0
  35. package/dist/src/decision/telemetry/worker.js +69 -0
  36. package/dist/src/lint/rule-activation.js +15 -7
  37. package/dist/src/lint/runner.js +2 -2
  38. package/dist/src/mcp/server.mjs +2 -11
  39. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  40. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  41. package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
  42. package/dist/src/mission-protocol/codecs.js +8 -38
  43. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  44. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  45. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  46. package/dist/src/models/conductor-data-policy.mjs +190 -0
  47. package/dist/src/models/config-loader.js +19 -3
  48. package/dist/src/models/config-writer.js +27 -0
  49. package/dist/src/models/model-capabilities.v1.json +197 -3
  50. package/dist/src/models/provider-policy.js +7 -155
  51. package/dist/src/models/resolver.js +2 -293
  52. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  53. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  54. package/dist/src/models/worker-direct-readiness.js +9 -218
  55. package/dist/src/models/worker-health-readiness.js +105 -26
  56. package/dist/src/models/worker-installation.mjs +12 -14
  57. package/dist/src/models/worker-member-references.js +64 -0
  58. package/dist/src/models/worker-plan.js +317 -0
  59. package/dist/src/models/worker-selection-policy.js +217 -0
  60. package/dist/src/models/wrapper-deployment.js +1 -1
  61. package/dist/src/resources/web-release.js +76 -65
  62. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  63. package/dist/src/serve/shared-host-scheduler.js +25 -107
  64. package/dist/src/serve/shared-host-starvation.js +73 -0
  65. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  66. package/dist/src/serve/worker-artifacts.mjs +56 -0
  67. package/dist/src/serve/worker-command-request.js +68 -0
  68. package/dist/src/serve/worker-invocation-paths.js +144 -0
  69. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  70. package/dist/src/serve/worker-invocation.js +24 -291
  71. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  72. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  73. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  74. package/dist/src/storage/backends/fortemi.js +33 -29
  75. package/dist/src/storage/backends/logseq.js +16 -3
  76. package/dist/src/storage/backends/postgres.js +74 -33
  77. package/dist/src/storage/backends/postgrest.js +37 -13
  78. package/dist/src/storage/config.js +6 -1
  79. package/dist/src/uhp/config.js +4 -0
  80. package/package.json +9 -1
  81. package/schemas/mission-v1.schema.json +20 -19
  82. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  83. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  84. package/tools/agents/providers/claude.mjs +3 -30
  85. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  86. package/tools/agents/providers/codex.mjs +2 -8
  87. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  88. package/tools/agents/providers/rule-delivery.mjs +3 -16
  89. package/tools/agents/providers/rule-presentation.mjs +38 -0
  90. package/tools/plugin/package-plugins.mjs +1 -1
  91. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  92. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  93. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  94. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  95. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  96. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  97. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  98. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  99. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  100. package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
  101. package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
  102. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  103. package/tools/ralph-external/session-launcher.mjs +73 -217
  104. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -32,20 +32,26 @@
32
32
  * @issue #958
33
33
  */
34
34
  import { existsSync } from 'fs';
35
+ import { resolveSensitiveInput } from '../../auth/sensitive-input.js';
36
+ import { credentialReferenceInput } from '../../mcp/credentials.mjs';
35
37
  import { mkdir, readFile, readdir, rm, stat, writeFile } from 'fs/promises';
36
38
  import { dirname, join, relative, resolve, sep } from 'path';
37
39
  const LOGSEQ_CONFIG_DIR = 'logseq';
38
40
  const DEFAULT_API_URL = 'http://127.0.0.1:12315/api';
39
41
  export class LogseqAdapter {
42
+ sensitiveInput;
40
43
  /** Absolute path to the Logseq graph root. */
41
44
  graph;
42
45
  apiUrl;
43
46
  useApi;
47
+ apiTokenEnv;
44
48
  apiWarned = false;
45
- constructor(config) {
49
+ constructor(config, sensitiveInput = {}) {
50
+ this.sensitiveInput = sensitiveInput;
46
51
  this.graph = resolve(expandHome(config.graph));
47
52
  this.apiUrl = config.apiUrl ?? DEFAULT_API_URL;
48
53
  this.useApi = config.useApi ?? true;
54
+ this.apiTokenEnv = config.apiTokenEnv;
49
55
  }
50
56
  async init() {
51
57
  if (!existsSync(this.graph)) {
@@ -61,7 +67,14 @@ export class LogseqAdapter {
61
67
  async probeApi() {
62
68
  if (this.apiWarned)
63
69
  return;
64
- const token = process.env['LOGSEQ_API_TOKEN'];
70
+ const input = credentialReferenceInput(this.apiTokenEnv ?? 'LOGSEQ_API_TOKEN', 'Logseq apiTokenEnv');
71
+ // Keep the optional legacy environment path, including its missing-token warning.
72
+ const token = input.env
73
+ ? (this.sensitiveInput.environment ?? process.env)[input.env]
74
+ : await resolveSensitiveInput(input, this.sensitiveInput);
75
+ if (!token && this.apiTokenEnv !== undefined) {
76
+ throw new Error('storage(logseq): apiTokenEnv is not set; use ENV_NAME, file:ABSOLUTE_PATH or store:NAME.');
77
+ }
65
78
  if (!token) {
66
79
  this.apiWarned = true;
67
80
  process.stderr.write(`storage(logseq): API requested (useApi: true) but LOGSEQ_API_TOKEN is not set.\n` +
@@ -89,7 +102,7 @@ export class LogseqAdapter {
89
102
  }
90
103
  catch {
91
104
  this.apiWarned = true;
92
- process.stderr.write(`storage(logseq): API server at ${this.apiUrl} is not reachable.\n` +
105
+ process.stderr.write(`storage(logseq): API server is not reachable.\n` +
93
106
  ` AIWG will continue with direct file writes against the graph.\n` +
94
107
  ` Start Logseq with the API server enabled, or set "useApi": false to suppress.\n`);
95
108
  }
@@ -1,3 +1,5 @@
1
+ import { resolveSensitiveInput } from '../../auth/sensitive-input.js';
2
+ import { credentialReferenceInput } from '../../mcp/credentials.mjs';
1
3
  import { createHash } from 'node:crypto';
2
4
  import { loadFeaturePackage } from '../../features/runtime.js';
3
5
  import { STORAGE_BACKEND_CONTRACT, } from '../backend-contract.js';
@@ -27,6 +29,7 @@ export class PostgresBackendError extends Error {
27
29
  */
28
30
  export class PostgresStorageBackend {
29
31
  options;
32
+ sensitiveInput;
30
33
  descriptor = {
31
34
  contract: STORAGE_BACKEND_CONTRACT,
32
35
  backend: 'postgres-direct',
@@ -50,8 +53,9 @@ export class PostgresStorageBackend {
50
53
  statementTimeoutMs;
51
54
  lockTimeoutMs;
52
55
  idleTransactionTimeoutMs;
53
- constructor(options) {
56
+ constructor(options, sensitiveInput = {}) {
54
57
  this.options = options;
58
+ this.sensitiveInput = sensitiveInput;
55
59
  validateIdentity(options.tenant, 'tenant');
56
60
  validateIdentity(options.subsystem, 'subsystem');
57
61
  this.pool = options.pool;
@@ -69,7 +73,7 @@ export class PostgresStorageBackend {
69
73
  }
70
74
  async init() {
71
75
  if (!this.pool)
72
- this.pool = await createPool(this.options);
76
+ this.pool = await createPool(this.options, this.sensitiveInput);
73
77
  if (this.options.schemaMode !== 'migrate') {
74
78
  const schema = await this.runQuery('SELECT schema_version FROM aiwg_storage_schema WHERE singleton = true');
75
79
  if (Number(schema.rows[0]?.schema_version) !== 1) {
@@ -187,7 +191,7 @@ export class PostgresStorageBackend {
187
191
  }
188
192
  /** Hold an exported PostgreSQL snapshot open while parallel consumers page it. */
189
193
  async openSnapshotLease() {
190
- const exporter = await this.requiredPool().connect();
194
+ const exporter = await this.connect();
191
195
  let closed = false;
192
196
  try {
193
197
  await exporter.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
@@ -208,8 +212,11 @@ export class PostgresStorageBackend {
208
212
  try {
209
213
  await exporter.query('COMMIT');
210
214
  }
215
+ catch (error) {
216
+ throw classifyPostgresError(error);
217
+ }
211
218
  finally {
212
- exporter.release(false);
219
+ releaseClient(exporter, false);
213
220
  }
214
221
  },
215
222
  };
@@ -219,7 +226,7 @@ export class PostgresStorageBackend {
219
226
  await exporter.query('ROLLBACK');
220
227
  }
221
228
  catch { /* preserve original error */ }
222
- exporter.release(true);
229
+ releaseClient(exporter, true);
223
230
  throw classifyPostgresError(error);
224
231
  }
225
232
  }
@@ -228,7 +235,7 @@ export class PostgresStorageBackend {
228
235
  throw new PostgresBackendError('AIWG_POSTGRES_SNAPSHOT_INVALID', 'exported snapshot identifier is malformed');
229
236
  }
230
237
  const boundedLimit = bounded(limit, 1000, 1, 10_000, 'limit');
231
- const client = await this.requiredPool().connect();
238
+ const client = await this.connect();
232
239
  let failed = false;
233
240
  try {
234
241
  await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
@@ -252,7 +259,7 @@ export class PostgresStorageBackend {
252
259
  throw classifyPostgresError(error);
253
260
  }
254
261
  finally {
255
- client.release(failed);
262
+ releaseClient(client, failed);
256
263
  }
257
264
  }
258
265
  async changes(cursor) {
@@ -322,12 +329,17 @@ export class PostgresStorageBackend {
322
329
  });
323
330
  }
324
331
  async close() {
325
- if (this.ownsPool && this.pool)
326
- await this.pool.end();
332
+ try {
333
+ if (this.ownsPool && this.pool)
334
+ await this.pool.end();
335
+ }
336
+ catch (error) {
337
+ throw classifyPostgresError(error);
338
+ }
327
339
  this.pool = undefined;
328
340
  }
329
341
  async withTransaction(operation, isolation = 'SERIALIZABLE') {
330
- const client = await this.requiredPool().connect();
342
+ const client = await this.connect();
331
343
  let failed = false;
332
344
  try {
333
345
  await client.query(`BEGIN ISOLATION LEVEL ${isolation}`);
@@ -347,7 +359,7 @@ export class PostgresStorageBackend {
347
359
  throw classifyPostgresError(error);
348
360
  }
349
361
  finally {
350
- client.release(failed);
362
+ releaseClient(client, failed);
351
363
  }
352
364
  }
353
365
  assertMutationIdentity(mutation) {
@@ -356,6 +368,14 @@ export class PostgresStorageBackend {
356
368
  throw new PostgresBackendError('AIWG_POSTGRES_IDENTITY_MISMATCH', 'mutation identity is outside this backend tenant/subsystem');
357
369
  }
358
370
  }
371
+ async connect() {
372
+ try {
373
+ return await this.requiredPool().connect();
374
+ }
375
+ catch (error) {
376
+ throw classifyPostgresError(error);
377
+ }
378
+ }
359
379
  requiredPool() {
360
380
  if (!this.pool)
361
381
  throw new PostgresBackendError('AIWG_POSTGRES_NOT_INITIALIZED', 'call init() before using the PostgreSQL backend');
@@ -370,11 +390,18 @@ export class PostgresStorageBackend {
370
390
  }
371
391
  }
372
392
  }
373
- async function createPool(options) {
393
+ async function createPool(options, sensitiveInput) {
374
394
  const envName = options.connectionStringEnv ?? 'AIWG_POSTGRES_URL';
375
- const connectionString = process.env[envName];
395
+ const input = credentialReferenceInput(envName, 'PostgreSQL connectionStringEnv');
396
+ let connectionString;
397
+ try {
398
+ connectionString = input.env ? (sensitiveInput.environment ?? process.env)[input.env] : await resolveSensitiveInput(input, sensitiveInput);
399
+ }
400
+ catch (error) {
401
+ throw new PostgresBackendError('AIWG_POSTGRES_CREDENTIAL_UNAVAILABLE', error.message);
402
+ }
376
403
  if (!connectionString) {
377
- throw new PostgresBackendError('AIWG_POSTGRES_CREDENTIAL_UNAVAILABLE', `PostgreSQL connection locator ${envName} is not set`);
404
+ throw new PostgresBackendError('AIWG_POSTGRES_CREDENTIAL_UNAVAILABLE', 'PostgreSQL connectionStringEnv is not set; use ENV_NAME (default AIWG_POSTGRES_URL), file:ABSOLUTE_PATH or store:NAME.');
378
405
  }
379
406
  if (options.ssl === undefined) {
380
407
  throw new PostgresBackendError('AIWG_POSTGRES_TLS_REQUIRED', 'PostgreSQL remote connections require explicit ssl=require or ssl=verify-full');
@@ -382,24 +409,29 @@ async function createPool(options) {
382
409
  if (options.ssl === 'disable' && !isLoopbackPostgresUrl(connectionString)) {
383
410
  throw new PostgresBackendError('AIWG_POSTGRES_TLS_REQUIRED', 'ssl=disable is limited to explicit loopback development endpoints');
384
411
  }
385
- const pg = await loadFeaturePackage('pg');
386
- const defaultExport = pg.default;
387
- const Pool = (pg.Pool ?? defaultExport?.Pool);
388
- if (!Pool)
389
- throw new PostgresBackendError('AIWG_POSTGRES_DRIVER_INVALID', 'optional pg package does not export Pool');
390
- const pool = new Pool({
391
- connectionString,
392
- max: bounded(options.maxConnections, DEFAULT_POOL_MAX, 1, 100, 'maxConnections'),
393
- connectionTimeoutMillis: bounded(options.connectionTimeoutMs, DEFAULT_TIMEOUT_MS, 1, 600_000, 'connectionTimeoutMs'),
394
- statement_timeout: bounded(options.statementTimeoutMs, DEFAULT_TIMEOUT_MS, 1, 600_000, 'statementTimeoutMs'),
395
- application_name: options.applicationName ?? 'aiwg',
396
- ssl: options.ssl === 'disable' ? false : { rejectUnauthorized: options.ssl === 'verify-full' },
397
- });
398
- // pg emits idle-client failures at pool scope. Registering a listener keeps
399
- // server restarts from becoming uncaught process exceptions; failed clients
400
- // are evicted by pg and the next backend operation reconnects normally.
401
- pool.on?.('error', () => undefined);
402
- return pool;
412
+ try {
413
+ const pg = await loadFeaturePackage('pg');
414
+ const defaultExport = pg.default;
415
+ const Pool = (pg.Pool ?? defaultExport?.Pool);
416
+ if (!Pool)
417
+ throw new PostgresBackendError('AIWG_POSTGRES_DRIVER_INVALID', 'optional pg package does not export Pool');
418
+ const pool = new Pool({
419
+ connectionString,
420
+ max: bounded(options.maxConnections, DEFAULT_POOL_MAX, 1, 100, 'maxConnections'),
421
+ connectionTimeoutMillis: bounded(options.connectionTimeoutMs, DEFAULT_TIMEOUT_MS, 1, 600_000, 'connectionTimeoutMs'),
422
+ statement_timeout: bounded(options.statementTimeoutMs, DEFAULT_TIMEOUT_MS, 1, 600_000, 'statementTimeoutMs'),
423
+ application_name: options.applicationName ?? 'aiwg',
424
+ ssl: options.ssl === 'disable' ? false : { rejectUnauthorized: options.ssl === 'verify-full' },
425
+ });
426
+ // pg emits idle-client failures at pool scope. Registering a listener keeps
427
+ // server restarts from becoming uncaught process exceptions; failed clients
428
+ // are evicted by pg and the next backend operation reconnects normally.
429
+ pool.on?.('error', () => undefined);
430
+ return pool;
431
+ }
432
+ catch (error) {
433
+ throw classifyPostgresError(error);
434
+ }
403
435
  }
404
436
  function isLoopbackPostgresUrl(connectionString) {
405
437
  try {
@@ -468,6 +500,15 @@ function classifyPostgresError(error) {
468
500
  if (['40001', '40P01', '55P03', '57014', '57P01', '57P02', '57P03', '08000', '08003', '08006', '08001'].includes(code)) {
469
501
  return new PostgresBackendError('AIWG_POSTGRES_RETRYABLE', `retryable PostgreSQL failure (${code})`, true);
470
502
  }
471
- return error instanceof Error ? error : new Error(String(error));
503
+ // Drivers may echo the entire connection string, passwords or server detail.
504
+ return new PostgresBackendError('AIWG_POSTGRES_OPERATION_FAILED', 'PostgreSQL operation failed; check connectivity, credentials and server configuration.');
505
+ }
506
+ function releaseClient(client, failed) {
507
+ try {
508
+ client.release(failed);
509
+ }
510
+ catch (error) {
511
+ throw classifyPostgresError(error);
512
+ }
472
513
  }
473
514
  //# sourceMappingURL=postgres.js.map
@@ -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 (error) {
199
- throw new PostgrestBackendError('AIWG_POSTGREST_TRANSPORT_FAILED', error instanceof Error ? error.message : String(error), true);
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
- const responseBody = await response.text();
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 databaseError = parseDatabaseError(responseBody);
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?.startsWith('AIWG_')
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 value = process.env[this.options.authorizationEnv];
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', `authorization locator ${this.options.authorizationEnv} is not set`);
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
- ...(typeof parsed.code === 'string' ? { databaseCode: parsed.code } : {}),
302
- ...(typeof parsed.message === 'string' ? { message: parsed.message } : {}),
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');
@@ -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))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.10.8",
3
+ "version": "2026.10.10",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,7 +49,11 @@
49
49
  "tools/ralph-external/session-launcher.mjs",
50
50
  "tools/ralph-external/process-monitor.mjs",
51
51
  "tools/ralph-external/lib/session-environment.mjs",
52
+ "tools/ralph-external/lib/worker-inputs.mjs",
53
+ "tools/ralph-external/lib/worker-final.mjs",
54
+ "tools/ralph-external/lib/worker-launch-cli.mjs",
52
55
  "tools/ralph-external/lib/worker-isolation.mjs",
56
+ "tools/ralph-external/lib/worker-native-sandbox.mjs",
53
57
  "tools/ralph-external/lib/systemd-client.mjs",
54
58
  "tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs",
55
59
  "tools/ralph-external/lib/launcher-recovery-evidence.mjs",
@@ -57,7 +61,11 @@
57
61
  "tools/ralph-external/lib/worker-overflow.mjs",
58
62
  "tools/scripts/resource-preflight.mjs",
59
63
  "tools/ralph-external/lib/provider-adapter.mjs",
64
+ "tools/ralph-external/lib/strict-worker-json.mjs",
65
+ "tools/ralph-external/lib/session-attempt.mjs",
66
+ "tools/ralph-external/lib/managed-worker-attempt.mjs",
60
67
  "tools/ralph-external/lib/worker-health.mjs",
68
+ "tools/ralph-external/lib/worker-artifacts.mjs",
61
69
  "tools/ralph-external/lib/claude-adapter.mjs",
62
70
  "tools/ralph-external/lib/codex-adapter.mjs",
63
71
  "tools/ralph-external/lib/opencode-adapter.mjs",
@@ -1643,6 +1643,7 @@
1643
1643
  "type": "object",
1644
1644
  "additionalProperties": false,
1645
1645
  "properties": {
1646
+ "invocationId": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,51}(?![\\s\\S])" },
1646
1647
  "isolation": {
1647
1648
  "enum": ["systemd-unit", "native-sandbox"]
1648
1649
  },
@@ -1703,28 +1704,24 @@
1703
1704
  ]
1704
1705
  },
1705
1706
  "artifacts": {
1706
- "type": "array",
1707
- "maxItems": 16,
1707
+ "type": "array", "maxItems": 16,
1708
+ "contains": { "type": "object", "required": ["location"], "properties": { "location": { "const": "workspace-relative" } } },
1709
+ "minContains": 0, "maxContains": 1,
1708
1710
  "items": {
1709
- "type": "object",
1710
- "additionalProperties": false,
1711
+ "type": "object", "additionalProperties": false,
1711
1712
  "properties": {
1712
- "name": {
1713
- "type": "string",
1714
- "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}(?![\\s\\S])"
1715
- },
1716
- "content": {
1717
- "type": "string",
1718
- "maxLength": 1048576
1719
- },
1720
- "location": {
1721
- "const": "run-directory"
1722
- }
1713
+ "name": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}(?![\\s\\S])" },
1714
+ "content": { "type": "string", "maxLength": 1048576 },
1715
+ "location": { "enum": ["run-directory", "workspace-relative"] },
1716
+ "prefix": { "type": "string", "minLength": 1, "maxLength": 256 },
1717
+ "relativePath": { "type": "string", "minLength": 1, "maxLength": 4096 }
1723
1718
  },
1724
- "required": [
1725
- "name",
1726
- "content"
1727
- ]
1719
+ "required": ["name", "content"],
1720
+ "allOf": [{
1721
+ "if": { "required": ["location"], "properties": { "location": { "const": "workspace-relative" } } },
1722
+ "then": { "required": ["prefix", "relativePath"] },
1723
+ "else": { "not": { "anyOf": [{ "required": ["prefix"] }, { "required": ["relativePath"] }] } }
1724
+ }]
1728
1725
  }
1729
1726
  },
1730
1727
  "briefPath": {
@@ -1871,6 +1868,10 @@
1871
1868
  "controlEvidence"
1872
1869
  ],
1873
1870
  "allOf": [
1871
+ {
1872
+ "if": { "required": ["artifacts"], "properties": { "artifacts": { "type": "array", "contains": { "type": "object", "required": ["location"], "properties": { "location": { "const": "workspace-relative" } } } } } },
1873
+ "then": { "required": ["invocationId"], "properties": { "transport": { "const": "shell" } } }
1874
+ },
1874
1875
  {
1875
1876
  "if": {
1876
1877
  "properties": {
@@ -184,6 +184,13 @@
184
184
  "type": "object",
185
185
  "additionalProperties": false,
186
186
  "properties": {
187
+ "baseRole": {
188
+ "enum": [
189
+ "efficiency",
190
+ "coding",
191
+ "reasoning"
192
+ ]
193
+ },
187
194
  "groups": {
188
195
  "type": "array",
189
196
  "items": {
@@ -248,6 +255,15 @@
248
255
  "type": "object",
249
256
  "additionalProperties": false,
250
257
  "properties": {
258
+ "localOnlyPaths": {
259
+ "type": "array",
260
+ "uniqueItems": true,
261
+ "items": {
262
+ "type": "string",
263
+ "pattern": "^(?!.*(?:^|/)\\.{1,2}(?:/|$))[A-Za-z0-9_.@+,-]+(?:/[A-Za-z0-9_.@+,-]+)*$(?![\\s\\S])"
264
+ },
265
+ "description": "Literal project-relative local-only files or directory subtrees; deployed conductor guards have limited shell coverage."
266
+ },
251
267
  "allowedProcessing": {
252
268
  "type": "array",
253
269
  "items": {
@@ -0,0 +1,35 @@
1
+ // Apply project settings and context after provider file deployment.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { initializeFrameworkWorkspace, ensureDir } from './base.mjs';
5
+ import { deployHookFile } from './claude.mjs';
6
+ import { deployConductorDataGuard } from './conductor-data-guard.mjs';
7
+
8
+ export async function postDeploy(targetDir, opts) {
9
+ initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
10
+ await deployConductorDataGuard(targetDir, 'claude', opts);
11
+
12
+ // Claude-specific post-deployment (settings.json, etc.)
13
+ const claudeDir = path.join(targetDir, '.claude');
14
+ const settingsPath = path.join(claudeDir, 'settings.json');
15
+
16
+ // If settings.json doesn't exist, create a minimal one
17
+ if (!fs.existsSync(settingsPath) && !opts.dryRun) {
18
+ ensureDir(claudeDir);
19
+ const settings = {
20
+ version: '1.0',
21
+ created: new Date().toISOString(),
22
+ aiwg: {
23
+ enabled: true,
24
+ mode: opts.mode || 'all'
25
+ }
26
+ };
27
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
28
+ console.log('Created .claude/settings.json');
29
+ }
30
+
31
+ // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
32
+ if (opts.srcRoot && opts.contextFiles !== false) {
33
+ deployHookFile(targetDir, opts);
34
+ }
35
+ }
@@ -1,3 +1,5 @@
1
+ import { postDeploy } from './claude-post-deploy.mjs';
2
+ export { postDeploy };
1
3
  import { importImpl } from '../../_resolve-impl.mjs';
2
4
  /**
3
5
  * Claude Code Provider
@@ -43,7 +45,6 @@ import {
43
45
  pruneStaleAiwgSkills,
44
46
  computeAllKernelNames,
45
47
  parseFrontmatter,
46
- initializeFrameworkWorkspace,
47
48
  filterAgentFiles,
48
49
  getAddonAgentFiles,
49
50
  getAddonCommandFiles,
@@ -395,34 +396,6 @@ export function createAgentsMd(target, srcRoot, dryRun) {
395
396
  // Post-Deployment
396
397
  // ============================================================================
397
398
 
398
- export async function postDeploy(targetDir, opts) {
399
- // Initialize framework workspace structure
400
- initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
401
-
402
- // Claude-specific post-deployment (settings.json, etc.)
403
- const claudeDir = path.join(targetDir, '.claude');
404
- const settingsPath = path.join(claudeDir, 'settings.json');
405
-
406
- // If settings.json doesn't exist, create a minimal one
407
- if (!fs.existsSync(settingsPath) && !opts.dryRun) {
408
- ensureDir(claudeDir);
409
- const settings = {
410
- version: '1.0',
411
- created: new Date().toISOString(),
412
- aiwg: {
413
- enabled: true,
414
- mode: opts.mode || 'all'
415
- }
416
- };
417
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
418
- console.log('Created .claude/settings.json');
419
- }
420
-
421
- // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
422
- if (opts.srcRoot && opts.contextFiles !== false) {
423
- deployHookFile(targetDir, opts);
424
- }
425
- }
426
399
 
427
400
  /**
428
401
  * Deploy the AIWG.md hook file and add @AIWG.md directive to CLAUDE.md.
@@ -444,7 +417,7 @@ function interpolateHookTokens(content, counts, targetDir) {
444
417
  });
445
418
  }
446
419
 
447
- function deployHookFile(targetDir, opts) {
420
+ export function deployHookFile(targetDir, opts) {
448
421
  const { srcRoot, dryRun, counts } = opts;
449
422
  const templatePath = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'templates', 'project', 'AIWG.md');
450
423
  const hookDest = path.join(targetDir, 'AIWG.md');
@@ -0,0 +1,13 @@
1
+ // Apply project settings and context after provider file deployment.
2
+ import { initializeFrameworkWorkspace } from './base.mjs';
3
+ import { createAgentsMd } from './codex.mjs';
4
+ import { deployConductorDataGuard } from './conductor-data-guard.mjs';
5
+
6
+ export async function postDeploy(targetDir, opts) {
7
+ initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
8
+ await deployConductorDataGuard(targetDir, 'codex', opts);
9
+
10
+ if (opts.createAgentsMd) {
11
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
12
+ }
13
+ }