@aiwg/cli 2026.10.6 → 2026.10.8
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/bin/aiwg.mjs +4 -2
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/cost-report.js +7 -1
- package/dist/src/cli/handlers/decision.js +36 -10
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +1 -1
- package/dist/src/cli/handlers/init.js +1 -1
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +33 -6
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/setup.js +24 -20
- package/dist/src/cli/handlers/use.js +57 -15
- package/dist/src/cli/prompt-utils.js +73 -0
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/cost/fleet-report.js +20 -22
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +19 -6
- 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/decision/qualification/gate-evidence.js +6 -4
- package/dist/src/decision/qualification/load-staged.js +47 -10
- package/dist/src/decision/qualification/runner.js +7 -3
- package/dist/src/discovery/interview.js +10 -5
- package/dist/src/discovery/plan-apply.js +26 -30
- package/dist/src/discovery/plan-validation.js +2 -2
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +2 -2
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +122 -109
- 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 +4 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- 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/mcp/tools/worker-invoke.mjs +13 -2
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/grok-build-models.js +20 -0
- package/dist/src/models/worker-installation.d.mts +1 -1
- package/dist/src/models/worker-installation.mjs +65 -4
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/provider-definitions.js +2 -2
- package/dist/src/serve/shared-host-scheduler.js +4 -1
- package/dist/src/serve/worker-invocation.js +10 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +9 -9
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +4 -12
- package/dist/src/storage/backends/fortemi.js +107 -49
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/package.json +1 -1
- package/tools/agents/providers/base.mjs +5 -1
- package/tools/agents/providers/claude.mjs +5 -2
- package/tools/agents/providers/codex.mjs +4 -1
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +5 -2
- package/tools/agents/providers/hermes.mjs +5 -2
- package/tools/agents/providers/omp.mjs +3 -1
- package/tools/agents/providers/rule-delivery.mjs +29 -12
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +6 -3
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/process-monitor.mjs +42 -4
- package/tools/ralph-external/session-launcher.mjs +125 -3
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
+
import { sensitiveInputOptions } from '../../auth/sensitive-input.js';
|
|
12
13
|
import { searchSkills } from '../../skills/registry.js';
|
|
13
14
|
import { installPackage, listInstalledPackages } from '../../packages/registry.js';
|
|
14
15
|
import { GitAdapter } from '../../packages/adapters/git.js';
|
|
@@ -24,7 +25,7 @@ const USAGE = [
|
|
|
24
25
|
' aiwg marketplace verify <package|lock> Verify cached content and receipts offline',
|
|
25
26
|
' aiwg marketplace export <package> --output <archive.json>',
|
|
26
27
|
' aiwg marketplace import <archive.json> [--project-local]',
|
|
27
|
-
' aiwg marketplace publish <source> --
|
|
28
|
+
' aiwg marketplace publish <source> --publisher <id> [--key-file <pem> | --key-stdin]',
|
|
28
29
|
' aiwg marketplace remove <catalog-id>',
|
|
29
30
|
' aiwg marketplace list List installed marketplace packages',
|
|
30
31
|
'',
|
|
@@ -33,6 +34,11 @@ const USAGE = [
|
|
|
33
34
|
' --project-local Store/read state below the target project .aiwg directory',
|
|
34
35
|
' --global Store/read state in the user AIWG directory (default)',
|
|
35
36
|
' --policy <name> Named trust policy or JSON policy path',
|
|
37
|
+
'Git fetch token sources:',
|
|
38
|
+
' --token-interactive | --token-stdin | --token-file <path> | --token-store <name> | --token-env <name>',
|
|
39
|
+
'Publish key sources (multiline PEM):',
|
|
40
|
+
' --key-interactive | --key-stdin | --key-file <path> | --key-store <name> | --key-env <name>',
|
|
41
|
+
' --key <path> Compatibility alias for --key-file (private file required)',
|
|
36
42
|
].join('\n');
|
|
37
43
|
function parseFlag(args, flag) {
|
|
38
44
|
const idx = args.indexOf(flag);
|
|
@@ -105,7 +111,10 @@ async function handleAdd(ctx) {
|
|
|
105
111
|
const requestedRef = parseFlag(ctx.args, '--ref') ?? 'HEAD';
|
|
106
112
|
const adapter = new GitAdapter();
|
|
107
113
|
const source = { gitUrl: argument, ref: requestedRef, label: argument };
|
|
108
|
-
const cachePath = await adapter.fetch(source, {
|
|
114
|
+
const cachePath = await adapter.fetch(source, {
|
|
115
|
+
refresh: hasFlag(ctx.args, '--refresh'),
|
|
116
|
+
tokenInput: sensitiveInputOptions(ctx.args, 'token'),
|
|
117
|
+
});
|
|
109
118
|
const identity = await inspectGitCheckout(cachePath);
|
|
110
119
|
const candidates = [
|
|
111
120
|
path.join(cachePath, 'aiwg-marketplace-catalog.json'),
|
|
@@ -251,6 +260,7 @@ async function handleInstall(ctx) {
|
|
|
251
260
|
}
|
|
252
261
|
const installed = await installPackage(rawRef, {
|
|
253
262
|
configDir,
|
|
263
|
+
tokenInput: sensitiveInputOptions(ctx.args, 'token'),
|
|
254
264
|
ref,
|
|
255
265
|
packageSelector: parseFlag(ctx.args, '--package'),
|
|
256
266
|
refresh: hasFlag(ctx.args, '--refresh'),
|
|
@@ -342,8 +352,8 @@ async function handlePublish(ctx) {
|
|
|
342
352
|
return argument;
|
|
343
353
|
const privateKeyPath = parseFlag(ctx.args, '--key');
|
|
344
354
|
const publisher = parseFlag(ctx.args, '--publisher');
|
|
345
|
-
if (!
|
|
346
|
-
return { exitCode: 1, message: 'Error: Usage: aiwg marketplace publish <source> --key <ed25519.pem> --publisher <id> [--output <dir>]' };
|
|
355
|
+
if (!publisher) {
|
|
356
|
+
return { exitCode: 1, message: 'Error: Usage: aiwg marketplace publish <source> [--key-file <ed25519.pem> | --key-stdin] --publisher <id> [--output <dir>]' };
|
|
347
357
|
}
|
|
348
358
|
try {
|
|
349
359
|
const outputDir = parseFlag(ctx.args, '--output') ?? path.join(path.resolve(argument), '.aiwg', 'marketplace');
|
|
@@ -355,6 +365,7 @@ async function handlePublish(ctx) {
|
|
|
355
365
|
sourceDir: argument,
|
|
356
366
|
outputDir,
|
|
357
367
|
privateKeyPath,
|
|
368
|
+
keyInput: sensitiveInputOptions(ctx.args, 'key'),
|
|
358
369
|
publicKeyPath: parseFlag(ctx.args, '--public-key'),
|
|
359
370
|
keyId: parseFlag(ctx.args, '--key-id'),
|
|
360
371
|
publisher,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { boundedSpawnSync } from '../../process/bounded-child-process.mjs';
|
|
2
|
+
import { helperEnvironment, HELPER_ENV_ALLOWLIST } from '../../process/cli-command.mjs';
|
|
3
|
+
import { readPrivateValueFile } from '../../auth/sensitive-input.js';
|
|
4
|
+
import { setupJev } from '../../decision/driver.js';
|
|
2
5
|
import { existsSync } from 'fs';
|
|
3
6
|
import { emptyConfig, FORCE_PUSH_POLICY_ALIAS_NOTE, normalizeForcePushPolicy as normalizeForcePushPolicyShared, getConfigPath, getProjectDir, readAiwgConfig, resolveRemoteProvider, VALID_PROVIDERS, writeAiwgConfig, } from '../../config/aiwg-config.js';
|
|
4
7
|
import { AiwgError, EXIT_CODES } from '../errors.js';
|
|
@@ -133,6 +136,7 @@ export function parseSetupProjectOptions(ctx) {
|
|
|
133
136
|
}
|
|
134
137
|
export function detectGitRemotes(projectDir) {
|
|
135
138
|
const result = boundedSpawnSync('git', ['-C', projectDir, 'remote', '-v'], 'quick', {
|
|
139
|
+
env: helperEnvironment(),
|
|
136
140
|
encoding: 'utf-8',
|
|
137
141
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
138
142
|
});
|
|
@@ -156,6 +160,7 @@ export function detectGitRemotes(projectDir) {
|
|
|
156
160
|
}
|
|
157
161
|
function detectDefaultBranch(projectDir) {
|
|
158
162
|
const symbolic = boundedSpawnSync('git', ['-C', projectDir, 'symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], 'quick', {
|
|
163
|
+
env: helperEnvironment(),
|
|
159
164
|
encoding: 'utf-8',
|
|
160
165
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
161
166
|
});
|
|
@@ -163,6 +168,7 @@ function detectDefaultBranch(projectDir) {
|
|
|
163
168
|
if (symbolic.status === 0 && fromOrigin)
|
|
164
169
|
return fromOrigin;
|
|
165
170
|
const branch = boundedSpawnSync('git', ['-C', projectDir, 'branch', '--show-current'], 'quick', {
|
|
171
|
+
env: helperEnvironment(),
|
|
166
172
|
encoding: 'utf-8',
|
|
167
173
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
168
174
|
});
|
|
@@ -170,6 +176,7 @@ function detectDefaultBranch(projectDir) {
|
|
|
170
176
|
}
|
|
171
177
|
function gitConfig(projectDir, key) {
|
|
172
178
|
const result = boundedSpawnSync('git', ['-C', projectDir, 'config', '--get', key], 'quick', {
|
|
179
|
+
env: helperEnvironment(),
|
|
173
180
|
encoding: 'utf-8',
|
|
174
181
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
175
182
|
});
|
|
@@ -656,7 +663,7 @@ function defaultGuidedApplyIO(plan, fresh, ctx, projectDir, frameworkRoot, canPr
|
|
|
656
663
|
chunks.push(Buffer.from(chunk));
|
|
657
664
|
return Buffer.concat(chunks).toString('utf8');
|
|
658
665
|
} } : {}),
|
|
659
|
-
readCredentialFile: async (path) =>
|
|
666
|
+
readCredentialFile: async (path) => readPrivateValueFile(path, '--credential'),
|
|
660
667
|
// stdin-at-apply prompts for the value without echoing it anywhere: the
|
|
661
668
|
// label names the consumer only, and the value never reaches output.
|
|
662
669
|
...(canPrompt ? { promptCredential: async (label) => {
|
|
@@ -674,26 +681,20 @@ function defaultGuidedApplyIO(plan, fresh, ctx, projectDir, frameworkRoot, canPr
|
|
|
674
681
|
await writeSetupPlan(projectDir, next);
|
|
675
682
|
},
|
|
676
683
|
owners: {
|
|
677
|
-
configureJev: async ({ action, token, options
|
|
684
|
+
configureJev: async ({ action, token, options }) => {
|
|
678
685
|
if (options != null)
|
|
679
686
|
validateJevOptions(options);
|
|
680
687
|
const settings = (options ?? {});
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
cwd: action.workspace, env: environment, input: token ? `${token}\n` : '', encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
692
|
-
maxBuffer: 1024 * 1024,
|
|
693
|
-
});
|
|
694
|
-
if (child.error || child.status !== 0)
|
|
695
|
-
throw new Error('Jev setup failed.');
|
|
696
|
-
if (settings.verify === true && settings.remove !== true && JSON.parse(child.stdout).verified !== true) {
|
|
688
|
+
// Invoke the existing owner directly. spawnSync's input option is
|
|
689
|
+
// rendered by NODE_DEBUG=child_process before the child can read it.
|
|
690
|
+
const result = await setupJev({
|
|
691
|
+
token,
|
|
692
|
+
...(typeof settings.region === 'string' ? { region: settings.region } : {}),
|
|
693
|
+
...(typeof settings.endpoint === 'string' ? { endpoint: settings.endpoint } : {}),
|
|
694
|
+
verify: settings.verify === true,
|
|
695
|
+
remove: settings.remove === true,
|
|
696
|
+
}, { env: process.env, cwd: action.workspace });
|
|
697
|
+
if (settings.verify === true && settings.remove !== true && result.verified !== true) {
|
|
697
698
|
throw new Error('Jev verification incomplete.');
|
|
698
699
|
}
|
|
699
700
|
},
|
|
@@ -711,7 +712,10 @@ function defaultGuidedApplyIO(plan, fresh, ctx, projectDir, frameworkRoot, canPr
|
|
|
711
712
|
await writeAiwgConfig(action.workspace, next);
|
|
712
713
|
const child = boundedSpawnSync(process.execPath, [join(frameworkRoot, 'bin', 'aiwg.mjs'),
|
|
713
714
|
'doctor', '--probe-workers', '--qualify', '--yes', '--json'], 'long', {
|
|
714
|
-
cwd: action.workspace,
|
|
715
|
+
cwd: action.workspace, env: helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST,
|
|
716
|
+
'AIWG_ROOT', 'AIWG_HOME', 'AIWG_CONFIG', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME',
|
|
717
|
+
'AIWG_ARTIFACTS_PATH', 'AIWG_PROJECT_ARTIFACTS_PATH', 'AIWG_PROJECT_AIWG_DIR',
|
|
718
|
+
'AIWG_PROJECT_MEMORY_HOME', 'NO_UPDATE_NOTIFIER', 'NO_COLOR', 'CI']), encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 1024 * 1024,
|
|
715
719
|
});
|
|
716
720
|
if (child.error || child.status !== 0)
|
|
717
721
|
throw new Error('Worker qualification failed.');
|
|
@@ -813,7 +817,7 @@ Options:
|
|
|
813
817
|
--intent code|docs|ops|mixed Workspace usage intent
|
|
814
818
|
--jev enable|decline|defer Optional external decision service (default off)
|
|
815
819
|
--region <r> --endpoint <url> --verify --remove --token-stdin Existing Jev options
|
|
816
|
-
--credential <provider>=
|
|
820
|
+
--credential <provider>=stdin|file:PATH|store:NAME|env:NAME Credential reference only
|
|
817
821
|
--yes Confirm writes without prompting
|
|
818
822
|
--dry-run Preview the setup plan
|
|
819
823
|
--review Render the saved guided plan per workspace/provider (writes nothing)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ContextWriteJournal } from '../../smiths/context-pipeline/context-file-safety.js';
|
|
1
2
|
import { trackTemporaryDirectory } from '../../utils/temporary-directory.js';
|
|
2
3
|
/**
|
|
3
4
|
* Use Command Handler
|
|
@@ -1675,6 +1676,11 @@ export async function countPiAgentFiles(target, agentsPath) {
|
|
|
1675
1676
|
// and additive passes can describe the same destination more than once.
|
|
1676
1677
|
let usePreviewActions;
|
|
1677
1678
|
// Recursive framework/addon/provider passes share the boundary's resolved policy.
|
|
1679
|
+
async function generateDeploymentContextFiles(options) {
|
|
1680
|
+
const result = await generateContextFiles(options);
|
|
1681
|
+
useDeployment?.contextFiles.push(result);
|
|
1682
|
+
return result;
|
|
1683
|
+
}
|
|
1678
1684
|
let useDeployment;
|
|
1679
1685
|
function useDeploymentArgs(args) {
|
|
1680
1686
|
const forwarded = [...args];
|
|
@@ -2118,7 +2124,7 @@ async function generateGlobalProjectContext(opts) {
|
|
|
2118
2124
|
skills: userPaths.skills,
|
|
2119
2125
|
behaviors: userPaths.behaviors,
|
|
2120
2126
|
});
|
|
2121
|
-
await
|
|
2127
|
+
await generateDeploymentContextFiles({
|
|
2122
2128
|
provider: opts.provider,
|
|
2123
2129
|
projectPath: opts.projectPath,
|
|
2124
2130
|
sections,
|
|
@@ -2181,7 +2187,7 @@ async function ensurePostDeployPhases(opts) {
|
|
|
2181
2187
|
behaviors: paths.behaviors,
|
|
2182
2188
|
});
|
|
2183
2189
|
try {
|
|
2184
|
-
await
|
|
2190
|
+
await generateDeploymentContextFiles({
|
|
2185
2191
|
provider: opts.provider,
|
|
2186
2192
|
projectPath: opts.projectPath,
|
|
2187
2193
|
sections,
|
|
@@ -2194,8 +2200,8 @@ async function ensurePostDeployPhases(opts) {
|
|
|
2194
2200
|
},
|
|
2195
2201
|
});
|
|
2196
2202
|
}
|
|
2197
|
-
catch {
|
|
2198
|
-
|
|
2203
|
+
catch (error) {
|
|
2204
|
+
throw new Error(`Context generation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
2199
2205
|
}
|
|
2200
2206
|
}
|
|
2201
2207
|
async function deploySourceDirectory(opts) {
|
|
@@ -2544,11 +2550,40 @@ export class UseHandler {
|
|
|
2544
2550
|
useDeployment = {
|
|
2545
2551
|
version: previousDeployment?.version ?? resolveDeployVersion(ctx.frameworkRoot || await getFrameworkRoot()),
|
|
2546
2552
|
skipContextFiles: previousDeployment?.skipContextFiles || ctx.args.includes('--no-context-files'),
|
|
2553
|
+
contextFiles: previousDeployment?.contextFiles ?? [],
|
|
2547
2554
|
};
|
|
2555
|
+
const journal = previousDeployment ? undefined : new ContextWriteJournal();
|
|
2548
2556
|
try {
|
|
2549
|
-
|
|
2557
|
+
let result;
|
|
2558
|
+
try {
|
|
2559
|
+
result = await this.executeDeployment(ctx);
|
|
2560
|
+
}
|
|
2561
|
+
catch (error) {
|
|
2562
|
+
result = { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
|
|
2563
|
+
}
|
|
2564
|
+
if (journal) {
|
|
2565
|
+
const rollback = result.exitCode !== 0 ? journal.rollback() : undefined;
|
|
2566
|
+
if (ctx.args.includes('--json')) {
|
|
2567
|
+
let structured;
|
|
2568
|
+
try {
|
|
2569
|
+
structured = JSON.parse(result.message || '{}');
|
|
2570
|
+
}
|
|
2571
|
+
catch {
|
|
2572
|
+
structured = { exitCode: result.exitCode, message: result.message };
|
|
2573
|
+
}
|
|
2574
|
+
structured.contextFiles = useDeployment.contextFiles;
|
|
2575
|
+
if (rollback)
|
|
2576
|
+
structured.contextRollback = rollback;
|
|
2577
|
+
result = { ...result, message: JSON.stringify(structured, null, 2), rawOutput: true };
|
|
2578
|
+
}
|
|
2579
|
+
else if (rollback?.conflicts.length) {
|
|
2580
|
+
result.message = [result.message, ...rollback.conflicts.map(conflict => `Context rollback conflict: ${conflict.path}: ${conflict.reason}`)].filter(Boolean).join('\n');
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
return result;
|
|
2550
2584
|
}
|
|
2551
2585
|
finally {
|
|
2586
|
+
journal?.dispose();
|
|
2552
2587
|
useDeployment = previousDeployment;
|
|
2553
2588
|
}
|
|
2554
2589
|
}
|
|
@@ -2639,6 +2674,9 @@ export class UseHandler {
|
|
|
2639
2674
|
try {
|
|
2640
2675
|
coreResult = await this.executeCore({ ...ctx, args: coreArgs });
|
|
2641
2676
|
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
coreResult = { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
|
|
2679
|
+
}
|
|
2642
2680
|
finally {
|
|
2643
2681
|
usePreviewActions = previousPreview;
|
|
2644
2682
|
if (json || dryRun) {
|
|
@@ -2664,12 +2702,18 @@ export class UseHandler {
|
|
|
2664
2702
|
else {
|
|
2665
2703
|
if (coreResult.exitCode === 0 && !dryRun && !VALID_FRAMEWORKS.includes(requestedBundle)) {
|
|
2666
2704
|
for (const provider of providers) {
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2705
|
+
try {
|
|
2706
|
+
await ensurePostDeployPhases({
|
|
2707
|
+
frameworkRoot,
|
|
2708
|
+
projectPath: projectDir,
|
|
2709
|
+
provider,
|
|
2710
|
+
args: remainingArgs,
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
catch (error) {
|
|
2714
|
+
coreResult = { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
|
|
2715
|
+
break;
|
|
2716
|
+
}
|
|
2673
2717
|
}
|
|
2674
2718
|
}
|
|
2675
2719
|
const providerResults = [];
|
|
@@ -4377,7 +4421,7 @@ export class UseHandler {
|
|
|
4377
4421
|
skills: paths.skills,
|
|
4378
4422
|
behaviors: paths.behaviors,
|
|
4379
4423
|
});
|
|
4380
|
-
const ctxResult = await
|
|
4424
|
+
const ctxResult = await generateDeploymentContextFiles({
|
|
4381
4425
|
provider: provider,
|
|
4382
4426
|
projectPath: target,
|
|
4383
4427
|
sections,
|
|
@@ -4431,10 +4475,8 @@ export class UseHandler {
|
|
|
4431
4475
|
}
|
|
4432
4476
|
}
|
|
4433
4477
|
catch (err) {
|
|
4434
|
-
// Non-fatal: context-pipeline emission must not block deployment.
|
|
4435
|
-
// Operator can re-run aiwg use to retry.
|
|
4436
4478
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4437
|
-
|
|
4479
|
+
return { exitCode: 1, message: `Context generation failed: ${msg}` };
|
|
4438
4480
|
}
|
|
4439
4481
|
}
|
|
4440
4482
|
span.end('use:complete', { framework });
|
|
@@ -88,7 +88,16 @@ export function askWithTimeout(rl, prompt, parse, fallback, fallbackLabel, timeo
|
|
|
88
88
|
const cleanup = () => {
|
|
89
89
|
clearTimeout(timer);
|
|
90
90
|
signal?.removeEventListener('abort', onAbort);
|
|
91
|
+
rl.removeListener('close', onClose);
|
|
91
92
|
};
|
|
93
|
+
const onClose = () => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
settled = true;
|
|
97
|
+
cleanup();
|
|
98
|
+
resolve(fallback);
|
|
99
|
+
};
|
|
100
|
+
rl.on('close', onClose);
|
|
92
101
|
if (signal?.aborted) {
|
|
93
102
|
// Fire synchronously so the caller's try/catch sees the reject without
|
|
94
103
|
// awaiting a tick.
|
|
@@ -112,6 +121,70 @@ export function askWithTimeout(rl, prompt, parse, fallback, fallbackLabel, timeo
|
|
|
112
121
|
export async function askString(rl, prompt, fallback = '', signal) {
|
|
113
122
|
return askWithTimeout(rl, prompt, (a) => a.trim(), fallback, fallback || '(empty)', promptTimeoutMs(), signal);
|
|
114
123
|
}
|
|
124
|
+
/** Collect a hidden paste through an empty line, with one overall prompt deadline. */
|
|
125
|
+
export function askMultilineSecret(rl, signal) {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const lines = [];
|
|
128
|
+
let length = 0;
|
|
129
|
+
let settled = false;
|
|
130
|
+
const cleanup = () => {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
signal?.removeEventListener('abort', onAbort);
|
|
133
|
+
rl.removeListener('line', onLine);
|
|
134
|
+
rl.removeListener('close', onClose);
|
|
135
|
+
lines.length = 0;
|
|
136
|
+
};
|
|
137
|
+
const finish = (value) => {
|
|
138
|
+
if (settled)
|
|
139
|
+
return;
|
|
140
|
+
settled = true;
|
|
141
|
+
cleanup();
|
|
142
|
+
resolve(value);
|
|
143
|
+
};
|
|
144
|
+
const onLine = (line) => {
|
|
145
|
+
if (!line) {
|
|
146
|
+
finish(lines.join('\n'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
length += line.length + (lines.length ? 1 : 0);
|
|
150
|
+
if (length > 16384) {
|
|
151
|
+
settled = true;
|
|
152
|
+
cleanup();
|
|
153
|
+
reject(new Error('Sensitive input exceeds 16384 characters.'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
lines.push(line);
|
|
157
|
+
};
|
|
158
|
+
// A closed terminal or timeout discards incomplete input, never echoes it.
|
|
159
|
+
const onClose = () => finish('');
|
|
160
|
+
const onAbort = () => {
|
|
161
|
+
if (settled)
|
|
162
|
+
return;
|
|
163
|
+
settled = true;
|
|
164
|
+
cleanup();
|
|
165
|
+
try {
|
|
166
|
+
rl.close();
|
|
167
|
+
}
|
|
168
|
+
catch { /* already closed */ }
|
|
169
|
+
reject(signal.reason ?? new Error('Aborted'));
|
|
170
|
+
};
|
|
171
|
+
const timeoutMs = promptTimeoutMs();
|
|
172
|
+
const timer = setTimeout(() => {
|
|
173
|
+
if (settled)
|
|
174
|
+
return;
|
|
175
|
+
ui.warn(` No input completed within ${Math.round(timeoutMs / 1000)}s — discarding incomplete sensitive input`);
|
|
176
|
+
finish('');
|
|
177
|
+
}, timeoutMs);
|
|
178
|
+
timer.unref?.();
|
|
179
|
+
rl.on('line', onLine);
|
|
180
|
+
rl.on('close', onClose);
|
|
181
|
+
if (signal?.aborted) {
|
|
182
|
+
onAbort();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
186
|
+
});
|
|
187
|
+
}
|
|
115
188
|
/**
|
|
116
189
|
* Prompt for a yes/no answer with timeout and default. Answers starting with
|
|
117
190
|
* 'y' (case-insensitive) are considered yes; everything else is no. On
|
|
@@ -13,6 +13,7 @@ import * as path from 'path';
|
|
|
13
13
|
import { execFile } from 'child_process';
|
|
14
14
|
import { promisify } from 'util';
|
|
15
15
|
import { boundedSpawnSync } from '../process/bounded-child-process.mjs';
|
|
16
|
+
import { helperEnvironment, HELPER_ENV_ALLOWLIST } from '../process/cli-command.mjs';
|
|
16
17
|
const execFileAsync = promisify(execFile);
|
|
17
18
|
const GITIGNORE_PROBE_BASENAME = '.aiwg-ignore-probe';
|
|
18
19
|
// ===========================
|
|
@@ -94,6 +95,7 @@ async function isGitWorktree(projectRoot) {
|
|
|
94
95
|
try {
|
|
95
96
|
const { stdout } = await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
96
97
|
cwd: projectRoot,
|
|
98
|
+
env: helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST, 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR']),
|
|
97
99
|
windowsHide: true,
|
|
98
100
|
});
|
|
99
101
|
return stdout.trim() === 'true';
|
|
@@ -117,6 +119,7 @@ async function resolveCoveredPatterns(projectRoot, patterns, lines) {
|
|
|
117
119
|
try {
|
|
118
120
|
await execFileAsync('git', ['check-ignore', '--quiet', '--', probePath(pattern)], {
|
|
119
121
|
cwd: projectRoot,
|
|
122
|
+
env: helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST, 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR']),
|
|
120
123
|
windowsHide: true,
|
|
121
124
|
});
|
|
122
125
|
return true;
|
|
@@ -239,6 +242,7 @@ export function dropGitignored(targetDir, files) {
|
|
|
239
242
|
}
|
|
240
243
|
const result = boundedSpawnSync('git', ['check-ignore', '-z', '--stdin'], 'quick', {
|
|
241
244
|
cwd: targetDir,
|
|
245
|
+
env: helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST, 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR']),
|
|
242
246
|
input: `${files.join('\0')}\0`,
|
|
243
247
|
encoding: 'utf8',
|
|
244
248
|
});
|
|
@@ -13,6 +13,7 @@ import { existsSync, statSync } from 'fs';
|
|
|
13
13
|
import { homedir } from 'os';
|
|
14
14
|
import { dirname, isAbsolute, relative, resolve } from 'path';
|
|
15
15
|
import { promisify } from 'util';
|
|
16
|
+
import { helperEnvironment } from '../process/cli-command.mjs';
|
|
16
17
|
import { getConfigPath, readAiwgConfig, resolveDelivery, resolveRemoteProvider, resolveRemotes, } from './aiwg-config.js';
|
|
17
18
|
const execFileAsync = promisify(execFile);
|
|
18
19
|
function expandHome(value) {
|
|
@@ -96,7 +97,7 @@ async function readRemoteUrl(repoPath, remote) {
|
|
|
96
97
|
if (remote.includes('://') || /^[^@/\s]+@[^:/\s]+:/.test(remote))
|
|
97
98
|
return remote;
|
|
98
99
|
try {
|
|
99
|
-
const { stdout } = await execFileAsync('git', ['-C', repoPath, 'remote', 'get-url', remote], { encoding: 'utf8' });
|
|
100
|
+
const { stdout } = await execFileAsync('git', ['-C', repoPath, 'remote', 'get-url', remote], { encoding: 'utf8', env: helperEnvironment() });
|
|
100
101
|
return stdout.trim() || undefined;
|
|
101
102
|
}
|
|
102
103
|
catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenRouter fleet spend observation and local activity correlation.
|
|
3
3
|
*
|
|
4
|
-
* Secrets are resolved
|
|
4
|
+
* Secrets are resolved from named sources or the user's
|
|
5
5
|
* ~/.config/aiwg/keys directory. Fleet configuration contains references,
|
|
6
6
|
* never credential values.
|
|
7
7
|
*
|
|
@@ -11,6 +11,7 @@ import { promises as fs } from 'node:fs';
|
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { load as loadYaml } from 'js-yaml';
|
|
14
|
+
import { resolveSensitiveInput } from '../auth/sensitive-input.js';
|
|
14
15
|
const OPENROUTER_API = 'https://openrouter.ai/api/v1';
|
|
15
16
|
const KEY_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
16
17
|
const SECRET_FIELD_PATTERN = /(?:api[_-]?key|token|secret|credential|authorization)/i;
|
|
@@ -60,6 +61,9 @@ function validateFleetConfig(value) {
|
|
|
60
61
|
if (!KEY_REF_PATTERN.test(entry.key_ref)) {
|
|
61
62
|
throw new Error(`fleet[${index}].key_ref contains unsupported characters.`);
|
|
62
63
|
}
|
|
64
|
+
if (entry.key_store !== undefined && (typeof entry.key_store !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(entry.key_store))) {
|
|
65
|
+
throw new Error(`fleet[${index}].key_store must name a native-store entry (1–128 letters, digits, dots, underscores or hyphens).`);
|
|
66
|
+
}
|
|
63
67
|
if (typeof entry.monthly_cap !== 'number' || !Number.isFinite(entry.monthly_cap) || entry.monthly_cap < 0) {
|
|
64
68
|
throw new Error(`fleet[${index}].monthly_cap must be a non-negative number.`);
|
|
65
69
|
}
|
|
@@ -73,6 +77,7 @@ function validateFleetConfig(value) {
|
|
|
73
77
|
bot: entry.bot.trim(),
|
|
74
78
|
machine: entry.machine.trim(),
|
|
75
79
|
key_ref: entry.key_ref,
|
|
80
|
+
...(entry.key_store !== undefined ? { key_store: entry.key_store } : {}),
|
|
76
81
|
monthly_cap: entry.monthly_cap,
|
|
77
82
|
};
|
|
78
83
|
});
|
|
@@ -101,10 +106,16 @@ export async function loadFleetKey(keyRef, options = {}) {
|
|
|
101
106
|
if (!KEY_REF_PATTERN.test(keyRef))
|
|
102
107
|
throw new Error('Invalid key_ref.');
|
|
103
108
|
const env = options.env ?? process.env;
|
|
109
|
+
const dependencies = { environment: env, readStore: options.readStore, isTTY: false };
|
|
110
|
+
// An explicit reference is scoped to this entry. Never probe a global key or
|
|
111
|
+
// another bot's store when the selected source is unavailable.
|
|
112
|
+
if (options.store !== undefined) {
|
|
113
|
+
return resolveSensitiveInput({ prefix: 'key', store: options.store }, dependencies);
|
|
114
|
+
}
|
|
104
115
|
const environmentName = keyEnvironmentName(keyRef);
|
|
105
116
|
const environmentValue = env[environmentName]?.trim();
|
|
106
117
|
if (environmentValue)
|
|
107
|
-
return
|
|
118
|
+
return resolveSensitiveInput({ prefix: 'key', env: environmentName }, dependencies);
|
|
108
119
|
const homeDir = options.homeDir ?? os.homedir();
|
|
109
120
|
const keyDirectory = path.resolve(homeDir, '.config', 'aiwg', 'keys');
|
|
110
121
|
const keyPath = path.resolve(keyDirectory, keyRef);
|
|
@@ -116,7 +127,7 @@ export async function loadFleetKey(keyRef, options = {}) {
|
|
|
116
127
|
}
|
|
117
128
|
catch (error) {
|
|
118
129
|
if (error.code === 'ENOENT') {
|
|
119
|
-
throw new Error(`No secure credential directory exists for key_ref '${keyRef}'; use ${environmentName} or create ~/.config/aiwg/keys with mode 0700.`);
|
|
130
|
+
throw new Error(`No secure credential directory exists for key_ref '${keyRef}'; use ${environmentName}, --key-store <name> (fleet: key_store), or create ~/.config/aiwg/keys with mode 0700.`);
|
|
120
131
|
}
|
|
121
132
|
throw error;
|
|
122
133
|
}
|
|
@@ -129,27 +140,12 @@ export async function loadFleetKey(keyRef, options = {}) {
|
|
|
129
140
|
if (typeof process.getuid === 'function' && directoryStat.uid !== process.getuid()) {
|
|
130
141
|
throw new Error('The AIWG credential directory must be owned by the current user.');
|
|
131
142
|
}
|
|
132
|
-
let stat;
|
|
133
143
|
try {
|
|
134
|
-
|
|
144
|
+
return await resolveSensitiveInput({ prefix: 'key', file: keyPath }, dependencies);
|
|
135
145
|
}
|
|
136
146
|
catch (error) {
|
|
137
|
-
|
|
138
|
-
throw new Error(`No credential found for key_ref '${keyRef}' in the secure key directory or ${environmentName}.`);
|
|
139
|
-
}
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
if (stat.isSymbolicLink() || !stat.isFile())
|
|
143
|
-
throw new Error(`Credential for key_ref '${keyRef}' must be a regular file, not a symlink.`);
|
|
144
|
-
if ((stat.mode & 0o077) !== 0)
|
|
145
|
-
throw new Error(`Credential file for key_ref '${keyRef}' must not be accessible by group or other users.`);
|
|
146
|
-
if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
|
|
147
|
-
throw new Error(`Credential file for key_ref '${keyRef}' must be owned by the current user.`);
|
|
147
|
+
throw new Error(`Credential for key_ref '${keyRef}': ${error.message} Use ${environmentName}, --key-store <name> (fleet: key_store), or a private ~/.config/aiwg/keys/<key_ref> file.`);
|
|
148
148
|
}
|
|
149
|
-
const value = (await fs.readFile(keyPath, 'utf8')).trim();
|
|
150
|
-
if (!value)
|
|
151
|
-
throw new Error(`Credential file for key_ref '${keyRef}' is empty.`);
|
|
152
|
-
return value;
|
|
153
149
|
}
|
|
154
150
|
function parseActivityLog(raw) {
|
|
155
151
|
const entries = [];
|
|
@@ -202,7 +198,9 @@ async function openRouterGet(endpoint, key, options) {
|
|
|
202
198
|
}
|
|
203
199
|
if (!payload || typeof payload !== 'object' || !payload.data)
|
|
204
200
|
throw new Error('OpenRouter returned an invalid response.');
|
|
205
|
-
|
|
201
|
+
// Generation metadata can reflect a request header in free-text fields. Keep
|
|
202
|
+
// such content out of both structured and human-readable reports.
|
|
203
|
+
return JSON.parse(JSON.stringify(payload.data, (_name, value) => (typeof value === 'string' ? value.split(key).join('[REDACTED]') : value)));
|
|
206
204
|
}
|
|
207
205
|
function correlateGenerations(records) {
|
|
208
206
|
const sessions = new Map();
|
|
@@ -223,7 +221,7 @@ function correlateGenerations(records) {
|
|
|
223
221
|
}
|
|
224
222
|
async function reportBot(entry, activity, options) {
|
|
225
223
|
try {
|
|
226
|
-
const key = await loadFleetKey(entry.key_ref, options);
|
|
224
|
+
const key = await loadFleetKey(entry.key_ref, { ...options, store: entry.key_store });
|
|
227
225
|
const keyData = await openRouterGet('/key', key, options);
|
|
228
226
|
const spend = Number(keyData.usage_monthly ?? keyData.usage ?? 0);
|
|
229
227
|
if (!Number.isFinite(spend) || spend < 0)
|
|
@@ -44,22 +44,15 @@ export class JevDecisionAdapter {
|
|
|
44
44
|
this.region = options.region?.trim() ? options.region : null;
|
|
45
45
|
this.observeResponseBody = options.observeResponseBody;
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return;
|
|
51
|
-
let parsed = {};
|
|
52
|
-
try {
|
|
53
|
-
parsed = asRecord(parseBoundedJson(body));
|
|
54
|
-
}
|
|
55
|
-
catch { /* Invalid answers still have bounded body evidence. */ }
|
|
56
|
-
let usage = emptyUsage();
|
|
47
|
+
/** An error response may still report billed usage and correlation metadata. */
|
|
48
|
+
parseErrorResponse(body) {
|
|
49
|
+
let parsed;
|
|
57
50
|
try {
|
|
58
|
-
|
|
51
|
+
parsed = parseBoundedJson(body);
|
|
59
52
|
}
|
|
60
|
-
catch { /*
|
|
61
|
-
this.
|
|
62
|
-
|
|
53
|
+
catch { /* Error classification does not require valid JSON. */ }
|
|
54
|
+
this.observeParsed(body, parsed);
|
|
55
|
+
return parsed;
|
|
63
56
|
}
|
|
64
57
|
/**
|
|
65
58
|
* Derives the observer's model/usage from a value `parseBoundedJson` has already produced (or `undefined`
|
|
@@ -81,6 +74,15 @@ export class JevDecisionAdapter {
|
|
|
81
74
|
catch { /* Missing usage is unknown, including on auth errors. */ }
|
|
82
75
|
return { actualModel: typeof record.model === 'string' && record.model ? record.model : null, usage };
|
|
83
76
|
}
|
|
77
|
+
responseMetadata(parsed, metadata) {
|
|
78
|
+
let body = {};
|
|
79
|
+
try {
|
|
80
|
+
body = asRecord(parsed);
|
|
81
|
+
}
|
|
82
|
+
catch { /* No body correlation metadata. */ }
|
|
83
|
+
const id = metadata.requestId ? null : safeRequestId(body.request_id ?? body.requestId);
|
|
84
|
+
return { ...metadata, ...this.observationMetadataOf(parsed), ...(id ? { requestId: id, requestIdSource: 'body' } : {}) };
|
|
85
|
+
}
|
|
84
86
|
/** Observes exactly once, from the single parse attempt `parsed` already represents (successful or not). */
|
|
85
87
|
observeParsed(body, parsed) {
|
|
86
88
|
this.observeResponseBody?.(body, this.observationMetadataOf(parsed));
|
|
@@ -209,22 +211,7 @@ export class JevDecisionAdapter {
|
|
|
209
211
|
if (!response.ok) {
|
|
210
212
|
try {
|
|
211
213
|
const errorBody = await readBoundedBody(response, signal);
|
|
212
|
-
this.
|
|
213
|
-
if (!metadata.requestId && response.headers.get('content-type')?.includes('json')) {
|
|
214
|
-
let parsedError;
|
|
215
|
-
try {
|
|
216
|
-
parsedError = parseBoundedJson(errorBody);
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
parsedError = null;
|
|
220
|
-
}
|
|
221
|
-
if (parsedError && typeof parsedError === 'object' && !Array.isArray(parsedError)) {
|
|
222
|
-
const field = parsedError.request_id ?? parsedError.requestId;
|
|
223
|
-
const safe = safeRequestId(field);
|
|
224
|
-
if (safe)
|
|
225
|
-
metadata = { ...metadata, requestId: safe, requestIdSource: 'body' };
|
|
226
|
-
}
|
|
227
|
-
}
|
|
214
|
+
metadata = this.responseMetadata(this.parseErrorResponse(errorBody), metadata);
|
|
228
215
|
}
|
|
229
216
|
catch {
|
|
230
217
|
if (request.signal.aborted)
|
|
@@ -248,6 +235,7 @@ export class JevDecisionAdapter {
|
|
|
248
235
|
finally {
|
|
249
236
|
this.observeParsed(encoded, parsed);
|
|
250
237
|
}
|
|
238
|
+
metadata = this.responseMetadata(parsed, metadata);
|
|
251
239
|
if (request.signal.aborted)
|
|
252
240
|
return externalInterruption(request, 'terminal-response', metadata);
|
|
253
241
|
if (deadline.aborted)
|
|
@@ -353,7 +341,7 @@ export class JevDecisionAdapter {
|
|
|
353
341
|
}
|
|
354
342
|
if (!response.ok) {
|
|
355
343
|
try {
|
|
356
|
-
this.
|
|
344
|
+
metadata = this.responseMetadata(this.parseErrorResponse(await readBoundedBody(response, signal)), metadata);
|
|
357
345
|
}
|
|
358
346
|
catch {
|
|
359
347
|
return batchFailure(requests, failure('invalid-output', metadata));
|
|
@@ -371,6 +359,7 @@ export class JevDecisionAdapter {
|
|
|
371
359
|
finally {
|
|
372
360
|
this.observeParsed(encoded, parsed);
|
|
373
361
|
}
|
|
362
|
+
metadata = this.responseMetadata(parsed, metadata);
|
|
374
363
|
const normalized = normalizeBatchResponse(requests, parsed).map(answer => ({
|
|
375
364
|
questionId: answer.questionId,
|
|
376
365
|
observation: { ...answer.observation, ...metadata },
|