agent-relay 4.0.35 → 4.0.37
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 +3 -6
- package/dist/index.cjs +776 -41
- package/dist/src/cli/bootstrap.js.map +1 -1
- package/dist/src/cli/commands/cloud.js.map +1 -1
- package/dist/src/cli/commands/core.d.ts.map +1 -1
- package/dist/src/cli/commands/core.js.map +1 -1
- package/dist/src/cli/commands/messaging.d.ts.map +1 -1
- package/dist/src/cli/commands/messaging.js.map +1 -1
- package/dist/src/cli/commands/monitoring.d.ts.map +1 -1
- package/dist/src/cli/commands/monitoring.js.map +1 -1
- package/node_modules/@agent-relay/cloud/dist/workflows.d.ts +12 -0
- package/node_modules/@agent-relay/cloud/dist/workflows.d.ts.map +1 -1
- package/node_modules/@agent-relay/cloud/dist/workflows.js +38 -0
- package/node_modules/@agent-relay/cloud/dist/workflows.js.map +1 -1
- package/node_modules/@agent-relay/cloud/package.json +2 -2
- package/node_modules/@agent-relay/config/package.json +2 -2
- package/node_modules/@agent-relay/hooks/package.json +4 -4
- package/node_modules/@agent-relay/sdk/dist/relay.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/relay.js.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-enforcement.test.d.ts +2 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-enforcement.test.d.ts.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-enforcement.test.js +437 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-enforcement.test.js.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-tracker.test.d.ts +2 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-tracker.test.d.ts.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-tracker.test.js +99 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/budget-tracker.test.js.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/proxy-env.test.d.ts +2 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/proxy-env.test.d.ts.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/proxy-env.test.js +135 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/proxy-env.test.js.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-custom.test.d.ts +2 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-custom.test.d.ts.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-custom.test.js +236 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-custom.test.js.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-traceback.test.d.ts +2 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-traceback.test.d.ts.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-traceback.test.js +448 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification-traceback.test.js.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification.test.js +71 -4
- package/node_modules/@agent-relay/sdk/dist/workflows/__tests__/verification.test.js.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/budget-tracker.d.ts +75 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/budget-tracker.d.ts.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/budget-tracker.js +180 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/budget-tracker.js.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/builder.d.ts +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/builder.js +17 -2
- package/node_modules/@agent-relay/sdk/dist/workflows/builder.js.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/index.d.ts +2 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/index.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/index.js +2 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/index.js.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/proxy-env.d.ts +52 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/proxy-env.d.ts.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/proxy-env.js +92 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/proxy-env.js.map +1 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/run-summary-table.d.ts +2 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/run-summary-table.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/run-summary-table.js +41 -9
- package/node_modules/@agent-relay/sdk/dist/workflows/run-summary-table.js.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/runner.d.ts +17 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/runner.js +390 -16
- package/node_modules/@agent-relay/sdk/dist/workflows/runner.js.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/types.d.ts +47 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/types.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/types.js.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/verification.d.ts +9 -0
- package/node_modules/@agent-relay/sdk/dist/workflows/verification.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/verification.js +78 -2
- package/node_modules/@agent-relay/sdk/dist/workflows/verification.js.map +1 -1
- package/node_modules/@agent-relay/sdk/package.json +7 -3
- package/node_modules/@agent-relay/telemetry/dist/config.d.ts.map +1 -1
- package/node_modules/@agent-relay/telemetry/dist/config.js +2 -4
- package/node_modules/@agent-relay/telemetry/dist/config.js.map +1 -1
- package/node_modules/@agent-relay/telemetry/dist/events.d.ts +1 -2
- package/node_modules/@agent-relay/telemetry/dist/events.d.ts.map +1 -1
- package/node_modules/@agent-relay/telemetry/dist/index.d.ts +1 -1
- package/node_modules/@agent-relay/telemetry/dist/index.d.ts.map +1 -1
- package/node_modules/@agent-relay/telemetry/dist/index.js +1 -1
- package/node_modules/@agent-relay/telemetry/dist/index.js.map +1 -1
- package/node_modules/@agent-relay/telemetry/package.json +1 -1
- package/node_modules/@agent-relay/trajectory/package.json +2 -2
- package/node_modules/@agent-relay/user-directory/package.json +2 -2
- package/node_modules/@agent-relay/utils/package.json +2 -2
- package/node_modules/@aws-sdk/core/dist-cjs/index.js +2 -0
- package/node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js +3 -0
- package/node_modules/@aws-sdk/core/dist-es/submodules/client/setFeature.js +2 -0
- package/node_modules/@aws-sdk/core/dist-types/submodules/client/setFeature.d.ts +1 -1
- package/node_modules/@aws-sdk/core/package.json +6 -4
- package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-http/package.json +5 -5
- package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
- package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
- package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
- package/node_modules/@aws-sdk/middleware-flexible-checksums/package.json +4 -4
- package/node_modules/@aws-sdk/middleware-sdk-s3/package.json +5 -5
- package/node_modules/@aws-sdk/middleware-user-agent/package.json +5 -5
- package/node_modules/@aws-sdk/nested-clients/package.json +18 -18
- package/node_modules/@aws-sdk/region-config-resolver/package.json +2 -2
- package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +2 -2
- package/node_modules/@aws-sdk/token-providers/package.json +3 -3
- package/node_modules/@aws-sdk/util-endpoints/package.json +2 -2
- package/node_modules/@aws-sdk/util-user-agent-node/package.json +2 -2
- package/node_modules/@relaycast/sdk/dist/version.d.ts +1 -1
- package/node_modules/@relaycast/sdk/dist/version.js +1 -1
- package/node_modules/@relaycast/sdk/node_modules/@relaycast/types/package.json +1 -1
- package/node_modules/@relaycast/sdk/package.json +2 -2
- package/node_modules/axios/CHANGELOG.md +112 -44
- package/node_modules/axios/README.md +30 -0
- package/node_modules/axios/dist/axios.js +34 -11
- package/node_modules/axios/dist/axios.js.map +1 -1
- package/node_modules/axios/dist/axios.min.js +2 -2
- package/node_modules/axios/dist/axios.min.js.map +1 -1
- package/node_modules/axios/dist/browser/axios.cjs +32 -6
- package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
- package/node_modules/axios/dist/esm/axios.js +32 -6
- package/node_modules/axios/dist/esm/axios.js.map +1 -1
- package/node_modules/axios/dist/esm/axios.min.js +2 -2
- package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
- package/node_modules/axios/dist/node/axios.cjs +91 -37
- package/node_modules/axios/dist/node/axios.cjs.map +1 -1
- package/node_modules/axios/index.d.cts +1 -0
- package/node_modules/axios/index.d.ts +1 -0
- package/node_modules/axios/lib/adapters/http.js +69 -22
- package/node_modules/axios/lib/core/mergeConfig.js +13 -1
- package/node_modules/axios/lib/env/data.js +1 -1
- package/node_modules/axios/lib/helpers/resolveConfig.js +14 -2
- package/node_modules/axios/lib/helpers/validator.js +3 -1
- package/node_modules/axios/package.json +1 -1
- package/package.json +9 -9
- package/packages/cloud/dist/workflows.d.ts +12 -0
- package/packages/cloud/dist/workflows.d.ts.map +1 -1
- package/packages/cloud/dist/workflows.js +38 -0
- package/packages/cloud/dist/workflows.js.map +1 -1
- package/packages/cloud/package.json +2 -2
- package/packages/config/package.json +2 -2
- package/packages/hooks/package.json +4 -4
- package/packages/sdk/dist/relay.d.ts.map +1 -1
- package/packages/sdk/dist/relay.js.map +1 -1
- package/packages/sdk/dist/workflows/__tests__/budget-enforcement.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/budget-enforcement.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/budget-enforcement.test.js +437 -0
- package/packages/sdk/dist/workflows/__tests__/budget-enforcement.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/budget-tracker.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/budget-tracker.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/budget-tracker.test.js +99 -0
- package/packages/sdk/dist/workflows/__tests__/budget-tracker.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/proxy-env.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/proxy-env.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/proxy-env.test.js +135 -0
- package/packages/sdk/dist/workflows/__tests__/proxy-env.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/verification-custom.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/verification-custom.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/verification-custom.test.js +236 -0
- package/packages/sdk/dist/workflows/__tests__/verification-custom.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/verification-traceback.test.d.ts +2 -0
- package/packages/sdk/dist/workflows/__tests__/verification-traceback.test.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/verification-traceback.test.js +448 -0
- package/packages/sdk/dist/workflows/__tests__/verification-traceback.test.js.map +1 -0
- package/packages/sdk/dist/workflows/__tests__/verification.test.js +71 -4
- package/packages/sdk/dist/workflows/__tests__/verification.test.js.map +1 -1
- package/packages/sdk/dist/workflows/budget-tracker.d.ts +75 -0
- package/packages/sdk/dist/workflows/budget-tracker.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/budget-tracker.js +180 -0
- package/packages/sdk/dist/workflows/budget-tracker.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +17 -2
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/index.d.ts +2 -0
- package/packages/sdk/dist/workflows/index.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/index.js +2 -0
- package/packages/sdk/dist/workflows/index.js.map +1 -1
- package/packages/sdk/dist/workflows/proxy-env.d.ts +52 -0
- package/packages/sdk/dist/workflows/proxy-env.d.ts.map +1 -0
- package/packages/sdk/dist/workflows/proxy-env.js +92 -0
- package/packages/sdk/dist/workflows/proxy-env.js.map +1 -0
- package/packages/sdk/dist/workflows/run-summary-table.d.ts +2 -1
- package/packages/sdk/dist/workflows/run-summary-table.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/run-summary-table.js +41 -9
- package/packages/sdk/dist/workflows/run-summary-table.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +17 -0
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +390 -16
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +47 -1
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/types.js.map +1 -1
- package/packages/sdk/dist/workflows/verification.d.ts +9 -0
- package/packages/sdk/dist/workflows/verification.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/verification.js +78 -2
- package/packages/sdk/dist/workflows/verification.js.map +1 -1
- package/packages/sdk/package.json +7 -3
- package/packages/telemetry/dist/config.d.ts.map +1 -1
- package/packages/telemetry/dist/config.js +2 -4
- package/packages/telemetry/dist/config.js.map +1 -1
- package/packages/telemetry/dist/events.d.ts +1 -2
- package/packages/telemetry/dist/events.d.ts.map +1 -1
- package/packages/telemetry/dist/index.d.ts +1 -1
- package/packages/telemetry/dist/index.d.ts.map +1 -1
- package/packages/telemetry/dist/index.js +1 -1
- package/packages/telemetry/dist/index.js.map +1 -1
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
|
@@ -16,10 +16,12 @@ import { stripAnsi as stripAnsiFn } from '../pty.js';
|
|
|
16
16
|
import { resolveSpawnPolicy } from '../spawn-from-env.js';
|
|
17
17
|
import { getCliDefinition } from '../cli-registry.js';
|
|
18
18
|
import { resolveCliSync } from '../cli-resolver.js';
|
|
19
|
+
import { buildNormalizedProxyEnv, getStrippedApiKeyVars, resolveProxyEnv, resolveProxyTokenFromEnv, resolveProxyUrlFromEnv, } from './proxy-env.js';
|
|
19
20
|
import { loadCustomSteps, resolveAllCustomSteps, validateCustomStepsUsage, CustomStepsParseError, CustomStepResolutionError, } from './custom-steps.js';
|
|
20
21
|
import { provisionWorkflowAgents, resolveAgentPermissions } from '../provisioner/index.js';
|
|
21
22
|
import { collectCliSession } from './cli-session-collector.js';
|
|
22
23
|
import { executeApiStep } from './api-executor.js';
|
|
24
|
+
import { BudgetExceededError, BudgetTracker } from './budget-tracker.js';
|
|
23
25
|
import { ChannelMessenger } from './channel-messenger.js';
|
|
24
26
|
import { InMemoryWorkflowDb } from './memory-db.js';
|
|
25
27
|
import { buildCommand as buildProcessCommand, spawnProcess } from './process-spawner.js';
|
|
@@ -52,6 +54,10 @@ const ENV_ALLOWLIST = new Set([
|
|
|
52
54
|
'RUST_BACKTRACE',
|
|
53
55
|
'RELAY_API_KEY',
|
|
54
56
|
'RELAYCAST_BASE_URL',
|
|
57
|
+
'RELAY_LLM_PROXY',
|
|
58
|
+
'RELAY_LLM_PROXY_URL',
|
|
59
|
+
'CREDENTIAL_PROXY_TOKEN',
|
|
60
|
+
'RELAY_LLM_PROXY_TOKEN',
|
|
55
61
|
'AGENT_RELAY_DASHBOARD_PORT',
|
|
56
62
|
'AGENT_RELAY_RUN_ID_FILE',
|
|
57
63
|
'EDITOR',
|
|
@@ -149,12 +155,16 @@ export class WorkflowRunner {
|
|
|
149
155
|
activeAgentHandles = new Map();
|
|
150
156
|
/** Per-agent workflow tokens for relay/relayfile auth across spawn modes. */
|
|
151
157
|
agentTokens = new Map();
|
|
158
|
+
/** Per-agent credential proxy tokens keyed by logical agent definition name. */
|
|
159
|
+
proxyTokens = new Map();
|
|
152
160
|
/** Per-agent relayfile mounts keyed by logical agent definition name. */
|
|
153
161
|
agentMounts = new Map();
|
|
154
162
|
// PTY-based output capture: accumulate terminal output per-agent
|
|
155
163
|
ptyOutputBuffers = new Map();
|
|
156
164
|
/** Snapshot of PTY output from the most recent failed attempt, keyed by step name. */
|
|
157
165
|
lastFailedStepOutput = new Map();
|
|
166
|
+
/** Most recent custom verification failure details, keyed by step name. */
|
|
167
|
+
lastCustomVerificationFailure = new Map();
|
|
158
168
|
ptyListeners = new Map();
|
|
159
169
|
ptyLogStreams = new Map();
|
|
160
170
|
/** Path to workers.json so `agents:kill` can find workflow-spawned agents */
|
|
@@ -185,6 +195,8 @@ export class WorkflowRunner {
|
|
|
185
195
|
activeReviewers = new Map();
|
|
186
196
|
/** Structured CLI session reports captured during the current run, keyed by step name. */
|
|
187
197
|
agentReports = new Map();
|
|
198
|
+
/** Optional per-run token budget tracker; only created when budgets are configured. */
|
|
199
|
+
budgetTracker;
|
|
188
200
|
static PTY_TASK_ARG_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB
|
|
189
201
|
processBackend;
|
|
190
202
|
constructor(options = {}) {
|
|
@@ -245,6 +257,51 @@ export class WorkflowRunner {
|
|
|
245
257
|
}
|
|
246
258
|
return { resolved, errors, warnings };
|
|
247
259
|
}
|
|
260
|
+
initializeBudgetTracker(config, workflow) {
|
|
261
|
+
const agentMap = new Map(config.agents.map((agent) => [agent.name, WorkflowRunner.resolveAgentDef(agent)]));
|
|
262
|
+
const stepConfigs = workflow.steps.flatMap((step) => {
|
|
263
|
+
if (step.type === 'deterministic' ||
|
|
264
|
+
step.type === 'worktree' ||
|
|
265
|
+
step.type === 'integration' ||
|
|
266
|
+
!step.agent) {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
const agentDef = agentMap.get(step.agent);
|
|
270
|
+
return [
|
|
271
|
+
{
|
|
272
|
+
stepName: step.name,
|
|
273
|
+
agentName: step.agent,
|
|
274
|
+
maxTokens: agentDef?.constraints?.maxTokens,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
});
|
|
278
|
+
const hasWorkflowBudget = config.swarm.tokenBudget !== undefined;
|
|
279
|
+
const hasAgentBudgets = stepConfigs.some((step) => step.maxTokens !== undefined);
|
|
280
|
+
this.budgetTracker =
|
|
281
|
+
hasWorkflowBudget || hasAgentBudgets
|
|
282
|
+
? new BudgetTracker({
|
|
283
|
+
workflowBudget: config.swarm.tokenBudget,
|
|
284
|
+
steps: stepConfigs,
|
|
285
|
+
})
|
|
286
|
+
: undefined;
|
|
287
|
+
}
|
|
288
|
+
ensureBudgetAllowsSpawn(stepName, agentName) {
|
|
289
|
+
if (!this.budgetTracker)
|
|
290
|
+
return;
|
|
291
|
+
const budgetCheck = this.budgetTracker.checkCanSpawn(stepName);
|
|
292
|
+
if (budgetCheck.allowed)
|
|
293
|
+
return;
|
|
294
|
+
const workflowBudget = this.budgetTracker.getRunSummaryBudgetData()?.workflow;
|
|
295
|
+
const used = workflowBudget?.used.toLocaleString('en-US') ?? '0';
|
|
296
|
+
const limit = workflowBudget?.limit?.toLocaleString('en-US') ?? '--';
|
|
297
|
+
this.log(`[budget] Skipping step ${stepName} — workflow budget exhausted (used ${used} of ${limit})`);
|
|
298
|
+
throw new BudgetExceededError(stepName, 'workflow', workflowBudget?.limit ?? 0, workflowBudget?.used ?? 0);
|
|
299
|
+
}
|
|
300
|
+
getTotalReportTokens(report) {
|
|
301
|
+
if (!report.tokens)
|
|
302
|
+
return undefined;
|
|
303
|
+
return report.tokens.input + report.tokens.output + report.tokens.cacheRead;
|
|
304
|
+
}
|
|
248
305
|
validatePermissions(agents, permissionProfiles, source = '<config>') {
|
|
249
306
|
const errors = [];
|
|
250
307
|
const warnings = [];
|
|
@@ -1091,16 +1148,169 @@ export class WorkflowRunner {
|
|
|
1091
1148
|
// Dashboard not running — silently ignore.
|
|
1092
1149
|
});
|
|
1093
1150
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1151
|
+
async loadCredentialProxyModule() {
|
|
1152
|
+
try {
|
|
1153
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
1154
|
+
const module = (await dynamicImport('@agent-relay/credential-proxy'));
|
|
1155
|
+
return typeof module.mintProxyToken === 'function' ? module : null;
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
if (error?.code === 'ERR_MODULE_NOT_FOUND') {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
resolveCredentialProxyProvider(agentDef, config) {
|
|
1165
|
+
const configuredProviders = Object.keys(config.swarm.credentialProxy?.providers ?? {});
|
|
1166
|
+
const explicitProvider = agentDef.credentials?.provider?.trim().toLowerCase();
|
|
1167
|
+
if (explicitProvider === 'openai' ||
|
|
1168
|
+
explicitProvider === 'anthropic' ||
|
|
1169
|
+
explicitProvider === 'openrouter') {
|
|
1170
|
+
return explicitProvider;
|
|
1171
|
+
}
|
|
1172
|
+
const model = agentDef.constraints?.model?.trim().toLowerCase() ?? '';
|
|
1173
|
+
if (model.includes('openrouter')) {
|
|
1174
|
+
return 'openrouter';
|
|
1175
|
+
}
|
|
1176
|
+
if (model.includes('claude') || model.includes('anthropic')) {
|
|
1177
|
+
return 'anthropic';
|
|
1178
|
+
}
|
|
1179
|
+
if (model.includes('openai') ||
|
|
1180
|
+
model.includes('chatgpt') ||
|
|
1181
|
+
model.includes('gpt') ||
|
|
1182
|
+
/\bo[134](?:\b|-)/.test(model)) {
|
|
1183
|
+
return 'openai';
|
|
1184
|
+
}
|
|
1185
|
+
if (configuredProviders.length === 1) {
|
|
1186
|
+
const [onlyProvider] = configuredProviders;
|
|
1187
|
+
if (onlyProvider === 'openai' || onlyProvider === 'anthropic' || onlyProvider === 'openrouter') {
|
|
1188
|
+
return onlyProvider;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
switch (agentDef.cli) {
|
|
1192
|
+
case 'claude':
|
|
1193
|
+
return 'anthropic';
|
|
1194
|
+
case 'codex':
|
|
1195
|
+
case 'aider':
|
|
1196
|
+
case 'goose':
|
|
1197
|
+
case 'opencode':
|
|
1198
|
+
case 'cursor':
|
|
1199
|
+
case 'cursor-agent':
|
|
1200
|
+
return 'openai';
|
|
1201
|
+
default:
|
|
1202
|
+
throw new Error(`Unable to resolve credential proxy provider for agent "${agentDef.name}". Set credentials.provider or constraints.model.`);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
resolveCredentialProxySecret(config) {
|
|
1206
|
+
const configuredSecret = config.swarm.credentialProxy?.jwtSecret;
|
|
1207
|
+
if (configuredSecret?.startsWith('$')) {
|
|
1208
|
+
const envSecret = process.env[configuredSecret.slice(1)];
|
|
1209
|
+
if (envSecret) {
|
|
1210
|
+
return envSecret;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
else if (configuredSecret) {
|
|
1214
|
+
return configuredSecret;
|
|
1215
|
+
}
|
|
1216
|
+
const defaultSecret = process.env.RELAY_PROXY_JWT_SECRET;
|
|
1217
|
+
if (defaultSecret) {
|
|
1218
|
+
return defaultSecret;
|
|
1219
|
+
}
|
|
1220
|
+
throw new Error('Credential proxy JWT secret is missing. Set swarm.credentialProxy.jwtSecret or RELAY_PROXY_JWT_SECRET.');
|
|
1221
|
+
}
|
|
1222
|
+
async mintAgentProxyToken(agentDef, config) {
|
|
1223
|
+
const proxyConfig = config.swarm?.credentialProxy;
|
|
1224
|
+
if (!proxyConfig?.proxyUrl || !agentDef.credentials?.proxy) {
|
|
1225
|
+
return undefined;
|
|
1226
|
+
}
|
|
1227
|
+
const provider = this.resolveCredentialProxyProvider(agentDef, config);
|
|
1228
|
+
const providerConfig = proxyConfig.providers?.[provider];
|
|
1229
|
+
const credentialId = providerConfig?.credentialId;
|
|
1230
|
+
if (!credentialId) {
|
|
1231
|
+
throw new Error(`Credential proxy provider "${provider}" is not configured for agent "${agentDef.name}".`);
|
|
1232
|
+
}
|
|
1233
|
+
const budget = agentDef.constraints?.maxTokens ?? proxyConfig.defaultBudget;
|
|
1234
|
+
const cacheKey = `${agentDef.name}:${provider}:${credentialId}:${budget ?? 'default'}`;
|
|
1235
|
+
const cachedToken = this.proxyTokens.get(cacheKey);
|
|
1236
|
+
if (cachedToken) {
|
|
1237
|
+
return cachedToken;
|
|
1238
|
+
}
|
|
1239
|
+
const credentialProxy = await this.loadCredentialProxyModule();
|
|
1240
|
+
if (!credentialProxy) {
|
|
1241
|
+
throw new Error('Credential proxy mode requires the optional peer dependency "@agent-relay/credential-proxy".');
|
|
1242
|
+
}
|
|
1243
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
1244
|
+
const token = await credentialProxy.mintProxyToken({
|
|
1245
|
+
sub: this.workspaceId,
|
|
1246
|
+
aud: 'relay-llm-proxy',
|
|
1247
|
+
provider,
|
|
1248
|
+
credentialId,
|
|
1249
|
+
budget,
|
|
1250
|
+
exp: nowSeconds + 15 * 60,
|
|
1251
|
+
}, this.resolveCredentialProxySecret(config));
|
|
1252
|
+
this.proxyTokens.set(cacheKey, token);
|
|
1253
|
+
return token;
|
|
1254
|
+
}
|
|
1255
|
+
async resolveAgentProxyMode(agentDef, config) {
|
|
1256
|
+
if (!agentDef.credentials?.proxy) {
|
|
1257
|
+
return undefined;
|
|
1258
|
+
}
|
|
1259
|
+
const env = this.getMergedRelayEnvSource();
|
|
1260
|
+
const configuredProxyUrl = config?.swarm?.credentialProxy?.proxyUrl;
|
|
1261
|
+
const proxyUrl = configuredProxyUrl ?? resolveProxyUrlFromEnv(env);
|
|
1262
|
+
if (!proxyUrl) {
|
|
1096
1263
|
return undefined;
|
|
1097
1264
|
}
|
|
1265
|
+
if (!configuredProxyUrl) {
|
|
1266
|
+
const injectedToken = resolveProxyTokenFromEnv(env);
|
|
1267
|
+
if (!injectedToken) {
|
|
1268
|
+
return undefined;
|
|
1269
|
+
}
|
|
1270
|
+
return {
|
|
1271
|
+
url: proxyUrl,
|
|
1272
|
+
token: injectedToken,
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
const token = await this.mintAgentProxyToken(agentDef, config);
|
|
1276
|
+
if (!token) {
|
|
1277
|
+
return undefined;
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
url: proxyUrl,
|
|
1281
|
+
token,
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
getMergedRelayEnvSource() {
|
|
1098
1285
|
return {
|
|
1099
1286
|
...process.env,
|
|
1100
1287
|
...(this.relayOptions.env ?? {}),
|
|
1101
1288
|
...(this.relayApiKey ? { RELAY_API_KEY: this.relayApiKey } : {}),
|
|
1102
1289
|
};
|
|
1103
1290
|
}
|
|
1291
|
+
getRelayEnv(proxyMode) {
|
|
1292
|
+
const env = this.getMergedRelayEnvSource();
|
|
1293
|
+
const inheritedProxyUrl = resolveProxyUrlFromEnv(env);
|
|
1294
|
+
const inheritedProxyToken = resolveProxyTokenFromEnv(env);
|
|
1295
|
+
if (!this.relayApiKey &&
|
|
1296
|
+
!this.relayOptions.env &&
|
|
1297
|
+
!proxyMode &&
|
|
1298
|
+
!(inheritedProxyUrl && inheritedProxyToken)) {
|
|
1299
|
+
return undefined;
|
|
1300
|
+
}
|
|
1301
|
+
const normalizedProxy = proxyMode?.url && proxyMode.token
|
|
1302
|
+
? proxyMode
|
|
1303
|
+
: inheritedProxyUrl && inheritedProxyToken
|
|
1304
|
+
? { url: inheritedProxyUrl, token: inheritedProxyToken }
|
|
1305
|
+
: undefined;
|
|
1306
|
+
if (normalizedProxy) {
|
|
1307
|
+
Object.assign(env, buildNormalizedProxyEnv(normalizedProxy.url, normalizedProxy.token));
|
|
1308
|
+
for (const key of getStrippedApiKeyVars()) {
|
|
1309
|
+
delete env[key];
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return env;
|
|
1313
|
+
}
|
|
1104
1314
|
async provisionAgents(config) {
|
|
1105
1315
|
// Cloud launcher already compiled and seeded relayfile ACLs before the
|
|
1106
1316
|
// sandbox started. Skip in-sandbox provisioning — the relayfile API has
|
|
@@ -1109,6 +1319,7 @@ export class WorkflowRunner {
|
|
|
1109
1319
|
return;
|
|
1110
1320
|
}
|
|
1111
1321
|
this.agentTokens.clear();
|
|
1322
|
+
this.proxyTokens.clear();
|
|
1112
1323
|
await this.stopProvisionedMounts();
|
|
1113
1324
|
const agentsToProvision = {};
|
|
1114
1325
|
for (const agent of config.agents) {
|
|
@@ -2042,6 +2253,7 @@ export class WorkflowRunner {
|
|
|
2042
2253
|
this.runtimeStepAgents.clear();
|
|
2043
2254
|
this.stepCompletionEvidence.clear();
|
|
2044
2255
|
this.agentReports.clear();
|
|
2256
|
+
this.initializeBudgetTracker(config, workflow);
|
|
2045
2257
|
this.log(`Starting workflow "${workflow.name}" (${workflow.steps.length} steps)`);
|
|
2046
2258
|
// Initialize trajectory recording
|
|
2047
2259
|
this.trajectory = new WorkflowTrajectory(config.trajectories, runId, this.cwd);
|
|
@@ -2371,6 +2583,7 @@ export class WorkflowRunner {
|
|
|
2371
2583
|
}
|
|
2372
2584
|
finally {
|
|
2373
2585
|
this.lastFailedStepOutput.clear();
|
|
2586
|
+
this.lastCustomVerificationFailure.clear();
|
|
2374
2587
|
for (const stream of this.ptyLogStreams.values())
|
|
2375
2588
|
stream.end();
|
|
2376
2589
|
this.ptyLogStreams.clear();
|
|
@@ -2973,6 +3186,7 @@ export class WorkflowRunner {
|
|
|
2973
3186
|
const specialistDef = WorkflowRunner.resolveAgentDef(rawAgentDef);
|
|
2974
3187
|
// API-mode agents: execute via direct API call instead of spawning a PTY/subprocess.
|
|
2975
3188
|
if (specialistDef.cli === 'api') {
|
|
3189
|
+
this.ensureBudgetAllowsSpawn(step.name, agentName);
|
|
2976
3190
|
const stepOutputContext = this.buildStepOutputContext(stepStates, runId);
|
|
2977
3191
|
const resolvedTask = this.interpolateStepTask(step.task ?? '', stepOutputContext);
|
|
2978
3192
|
state.row.status = 'running';
|
|
@@ -3050,6 +3264,8 @@ export class WorkflowRunner {
|
|
|
3050
3264
|
let lastAttemptStartedAt;
|
|
3051
3265
|
let lastEffectiveAgentDef;
|
|
3052
3266
|
let lastEffectiveCwd;
|
|
3267
|
+
let lastAttemptReportCaptured = false;
|
|
3268
|
+
let lastDiagnosticResult = null;
|
|
3053
3269
|
// OWNER_DECISION: INCOMPLETE_RETRY is enforced here at the attempt-loop level so every
|
|
3054
3270
|
// interactive execution path shares the same contract:
|
|
3055
3271
|
// - retries remaining => throw back into the loop and retry
|
|
@@ -3060,6 +3276,11 @@ export class WorkflowRunner {
|
|
|
3060
3276
|
// Reset per-attempt exit info so stale values don't leak across retries
|
|
3061
3277
|
lastExitCode = undefined;
|
|
3062
3278
|
lastExitSignal = undefined;
|
|
3279
|
+
lastAttemptStartedAt = undefined;
|
|
3280
|
+
lastEffectiveAgentDef = undefined;
|
|
3281
|
+
lastEffectiveCwd = undefined;
|
|
3282
|
+
lastAttemptReportCaptured = false;
|
|
3283
|
+
let stepOutputForDiagnostic = '';
|
|
3063
3284
|
if (attempt > 0) {
|
|
3064
3285
|
this.emit({ type: 'step:retrying', runId, stepName: step.name, attempt });
|
|
3065
3286
|
this.postToChannel(`**[${step.name}]** Retrying (attempt ${attempt + 1}/${maxRetries + 1})`);
|
|
@@ -3077,6 +3298,7 @@ export class WorkflowRunner {
|
|
|
3077
3298
|
await this.delay(retryDelay);
|
|
3078
3299
|
}
|
|
3079
3300
|
try {
|
|
3301
|
+
this.ensureBudgetAllowsSpawn(step.name, agentName);
|
|
3080
3302
|
lastAttemptStartedAt = Date.now();
|
|
3081
3303
|
// Mark step as running
|
|
3082
3304
|
state.row.status = 'running';
|
|
@@ -3114,12 +3336,29 @@ export class WorkflowRunner {
|
|
|
3114
3336
|
let resolvedTask = this.interpolateStepTask(step.task ?? '', stepOutputContext);
|
|
3115
3337
|
// On retry attempts, prepend failure context so the agent knows what went wrong
|
|
3116
3338
|
if (attempt > 0 && lastError) {
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3339
|
+
if (lastDiagnosticResult) {
|
|
3340
|
+
resolvedTask =
|
|
3341
|
+
`[RETRY — Attempt ${attempt + 1}/${maxRetries + 1}] Verification failed.\n` +
|
|
3342
|
+
`Diagnostic analysis:\n${lastDiagnosticResult.analysis}\n\n` +
|
|
3343
|
+
`Original error: ${lastError}\n---\n${resolvedTask}`;
|
|
3344
|
+
}
|
|
3345
|
+
else {
|
|
3346
|
+
const priorOutput = (this.lastFailedStepOutput.get(step.name) ?? '').slice(-2000);
|
|
3347
|
+
const customVerificationFailure = this.lastCustomVerificationFailure.get(step.name);
|
|
3348
|
+
const verificationFailurePrompt = customVerificationFailure
|
|
3349
|
+
? `[VERIFICATION FAILED] Your code did not pass the verification check.\n` +
|
|
3350
|
+
`Command: ${customVerificationFailure.command}\n` +
|
|
3351
|
+
`Output:\n` +
|
|
3352
|
+
`${customVerificationFailure.output}\n\n` +
|
|
3353
|
+
`Fix the issues above before proceeding.\n`
|
|
3354
|
+
: '';
|
|
3355
|
+
resolvedTask =
|
|
3356
|
+
`[RETRY — Attempt ${attempt + 1}/${maxRetries + 1}]\n` +
|
|
3357
|
+
`Previous attempt failed: ${lastError}\n` +
|
|
3358
|
+
verificationFailurePrompt +
|
|
3359
|
+
(priorOutput ? `Previous output (last 2000 chars):\n${priorOutput}\n` : '') +
|
|
3360
|
+
`---\n${resolvedTask}`;
|
|
3361
|
+
}
|
|
3123
3362
|
}
|
|
3124
3363
|
// If this is an interactive agent, append awareness of non-interactive workers
|
|
3125
3364
|
// so the lead knows not to message them and to use step output chaining instead
|
|
@@ -3160,6 +3399,7 @@ export class WorkflowRunner {
|
|
|
3160
3399
|
if (usesDedicatedOwner) {
|
|
3161
3400
|
const result = await this.executeSupervisedAgentStep(step, { specialist: effectiveSpecialist, owner: effectiveOwner, reviewer: reviewDef }, resolvedTask, timeoutMs, attempt);
|
|
3162
3401
|
specialistOutput = result.specialistOutput;
|
|
3402
|
+
stepOutputForDiagnostic = result.specialistOutput;
|
|
3163
3403
|
ownerOutput = result.ownerOutput;
|
|
3164
3404
|
ownerElapsed = result.ownerElapsed;
|
|
3165
3405
|
completionReason = result.completionReason;
|
|
@@ -3228,6 +3468,7 @@ export class WorkflowRunner {
|
|
|
3228
3468
|
}
|
|
3229
3469
|
}
|
|
3230
3470
|
specialistOutput = output;
|
|
3471
|
+
stepOutputForDiagnostic = output;
|
|
3231
3472
|
ownerOutput = output;
|
|
3232
3473
|
}
|
|
3233
3474
|
// Even non-interactive steps can emit an explicit OWNER_DECISION contract.
|
|
@@ -3281,6 +3522,7 @@ export class WorkflowRunner {
|
|
|
3281
3522
|
}
|
|
3282
3523
|
}
|
|
3283
3524
|
await this.captureAgentReport(runId, step.name, lastEffectiveAgentDef, lastEffectiveCwd, lastAttemptStartedAt, Date.now());
|
|
3525
|
+
lastAttemptReportCaptured = true;
|
|
3284
3526
|
// Mark completed
|
|
3285
3527
|
state.row.status = 'completed';
|
|
3286
3528
|
state.row.output = combinedOutput;
|
|
@@ -3310,6 +3552,26 @@ export class WorkflowRunner {
|
|
|
3310
3552
|
catch (err) {
|
|
3311
3553
|
lastError = err instanceof Error ? err.message : String(err);
|
|
3312
3554
|
lastCompletionReason = err instanceof WorkflowCompletionError ? err.completionReason : undefined;
|
|
3555
|
+
const diagnosticVerification = step.verification;
|
|
3556
|
+
if (err instanceof WorkflowCompletionError &&
|
|
3557
|
+
err.completionReason === 'failed_verification' &&
|
|
3558
|
+
diagnosticVerification?.diagnosticAgent &&
|
|
3559
|
+
attempt < maxRetries) {
|
|
3560
|
+
lastDiagnosticResult = await this.runDiagnosticAgent(step, lastError, stepOutputForDiagnostic || (this.lastFailedStepOutput.get(step.name) ?? ''), agentMap, runId);
|
|
3561
|
+
}
|
|
3562
|
+
else {
|
|
3563
|
+
lastDiagnosticResult = null;
|
|
3564
|
+
}
|
|
3565
|
+
if (lastCompletionReason !== 'failed_verification') {
|
|
3566
|
+
this.lastCustomVerificationFailure.delete(step.name);
|
|
3567
|
+
}
|
|
3568
|
+
if (!(err instanceof BudgetExceededError) && !lastAttemptReportCaptured) {
|
|
3569
|
+
await this.captureAgentReport(runId, step.name, lastEffectiveAgentDef, lastEffectiveCwd, lastAttemptStartedAt, Date.now());
|
|
3570
|
+
lastAttemptReportCaptured = true;
|
|
3571
|
+
}
|
|
3572
|
+
if (err instanceof BudgetExceededError) {
|
|
3573
|
+
break;
|
|
3574
|
+
}
|
|
3313
3575
|
if (lastCompletionReason === 'retry_requested_by_owner' && attempt >= maxRetries) {
|
|
3314
3576
|
lastError = this.buildOwnerRetryBudgetExceededMessage(step.name, maxRetries, lastError);
|
|
3315
3577
|
}
|
|
@@ -3330,7 +3592,9 @@ export class WorkflowRunner {
|
|
|
3330
3592
|
const verificationValue = typeof step.verification === 'object' && 'value' in step.verification
|
|
3331
3593
|
? String(step.verification.value)
|
|
3332
3594
|
: undefined;
|
|
3333
|
-
|
|
3595
|
+
if (!lastAttemptReportCaptured) {
|
|
3596
|
+
await this.captureAgentReport(runId, step.name, lastEffectiveAgentDef, lastEffectiveCwd, lastAttemptStartedAt, Date.now());
|
|
3597
|
+
}
|
|
3334
3598
|
await this.trajectory?.stepFailed(step, lastError ?? 'Unknown error', maxRetries + 1, maxRetries, {
|
|
3335
3599
|
agent: agentName,
|
|
3336
3600
|
nonInteractive,
|
|
@@ -3343,6 +3607,73 @@ export class WorkflowRunner {
|
|
|
3343
3607
|
}, lastCompletionReason);
|
|
3344
3608
|
throw new Error(`Step "${step.name}" failed after ${maxRetries} retries: ${lastError ?? 'Unknown error'}`);
|
|
3345
3609
|
}
|
|
3610
|
+
async runDiagnosticAgent(step, verificationError, stepOutput, agentMap, runId) {
|
|
3611
|
+
const verification = step.verification;
|
|
3612
|
+
const diagnosticAgentName = verification?.diagnosticAgent;
|
|
3613
|
+
if (!verification || !diagnosticAgentName) {
|
|
3614
|
+
return null;
|
|
3615
|
+
}
|
|
3616
|
+
const rawDiagnosticDef = agentMap.get(diagnosticAgentName);
|
|
3617
|
+
if (!rawDiagnosticDef) {
|
|
3618
|
+
this.log(`[${step.name}] Diagnostic agent "${diagnosticAgentName}" not found — falling back to standard retry`);
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3621
|
+
const diagnosticAgentDef = {
|
|
3622
|
+
...WorkflowRunner.resolveAgentDef(rawDiagnosticDef),
|
|
3623
|
+
interactive: false,
|
|
3624
|
+
};
|
|
3625
|
+
const verificationCommand = verification.type === 'custom' ? verification.value : `${verification.type}: ${verification.value}`;
|
|
3626
|
+
const diagnosticTimeout = verification.diagnosticTimeout ?? 60_000;
|
|
3627
|
+
const diagnosticPrompt = `The following verification failed after step "${step.name}".\n\n` +
|
|
3628
|
+
`Verification command: ${verificationCommand}\n` +
|
|
3629
|
+
`Verification output:\n${verificationError}\n\n` +
|
|
3630
|
+
`Step task was:\n${step.task ?? ''}\n\n` +
|
|
3631
|
+
`Step output (last 2000 chars):\n${stepOutput.slice(-2000)}\n\n` +
|
|
3632
|
+
`Analyze what went wrong. Be specific. Do NOT fix the code.`;
|
|
3633
|
+
const diagnosticStep = {
|
|
3634
|
+
...step,
|
|
3635
|
+
name: `${step.name}-diagnostic-${runId.slice(0, 8)}`,
|
|
3636
|
+
agent: diagnosticAgentName,
|
|
3637
|
+
task: diagnosticPrompt,
|
|
3638
|
+
verification: undefined,
|
|
3639
|
+
retries: 0,
|
|
3640
|
+
};
|
|
3641
|
+
const diagnosticCwd = this.resolveExecutionCwd(diagnosticStep, diagnosticAgentDef);
|
|
3642
|
+
const startedAt = Date.now();
|
|
3643
|
+
try {
|
|
3644
|
+
this.ensureBudgetAllowsSpawn(step.name, diagnosticAgentName);
|
|
3645
|
+
this.log(`[${step.name}] Verification failed — running diagnostic agent '${diagnosticAgentName}'...`);
|
|
3646
|
+
const diagnosticResult = await this.execNonInteractive(diagnosticAgentDef, diagnosticStep, diagnosticTimeout);
|
|
3647
|
+
const elapsedMs = Date.now() - startedAt;
|
|
3648
|
+
await this.captureAgentReport(runId, step.name, diagnosticAgentDef, diagnosticCwd, startedAt, Date.now());
|
|
3649
|
+
const analysis = diagnosticResult.output.trim();
|
|
3650
|
+
const tokenCount = Math.max(1, Math.ceil(analysis.length / 4));
|
|
3651
|
+
const firstLine = analysis
|
|
3652
|
+
.split(/\r?\n/)
|
|
3653
|
+
.map((line) => line.trim())
|
|
3654
|
+
.find(Boolean) ?? '(no analysis returned)';
|
|
3655
|
+
this.log(`[${step.name}] Diagnostic complete (${elapsedMs}ms, ${tokenCount} tokens): ${firstLine}`);
|
|
3656
|
+
return {
|
|
3657
|
+
analysis,
|
|
3658
|
+
metadata: {
|
|
3659
|
+
agentName: diagnosticAgentName,
|
|
3660
|
+
elapsedMs,
|
|
3661
|
+
tokenCount,
|
|
3662
|
+
},
|
|
3663
|
+
};
|
|
3664
|
+
}
|
|
3665
|
+
catch (error) {
|
|
3666
|
+
await this.captureAgentReport(runId, step.name, diagnosticAgentDef, diagnosticCwd, startedAt, Date.now());
|
|
3667
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3668
|
+
if (/\btimed out\b/i.test(message)) {
|
|
3669
|
+
this.log(`[${step.name}] Diagnostic timed out — falling back to standard retry`);
|
|
3670
|
+
}
|
|
3671
|
+
else {
|
|
3672
|
+
this.log(`[${step.name}] Diagnostic failed — falling back to standard retry: ${message}`);
|
|
3673
|
+
}
|
|
3674
|
+
return null;
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3346
3677
|
buildOwnerRetryBudgetExceededMessage(stepName, maxRetries, ownerDecisionError) {
|
|
3347
3678
|
const attempts = maxRetries + 1;
|
|
3348
3679
|
const prefix = `Step "${stepName}" `;
|
|
@@ -4340,7 +4671,11 @@ export class WorkflowRunner {
|
|
|
4340
4671
|
this.postToChannel(`**[${step.name}]** Assigned to \`${agentName}\` (non-interactive)`);
|
|
4341
4672
|
const stdoutChunks = [];
|
|
4342
4673
|
const stderrChunks = [];
|
|
4343
|
-
const
|
|
4674
|
+
const proxyMode = await this.resolveAgentProxyMode(agentDef, this.currentConfig);
|
|
4675
|
+
const env = { ...(this.getRelayEnv(proxyMode) ?? filteredEnv()) };
|
|
4676
|
+
if (proxyMode?.url && proxyMode.token) {
|
|
4677
|
+
Object.assign(env, resolveProxyEnv(agentDef.cli, proxyMode.url, proxyMode.token));
|
|
4678
|
+
}
|
|
4344
4679
|
const agentToken = this.agentTokens.get(agentDef.name);
|
|
4345
4680
|
const mount = this.agentMounts.get(agentDef.name);
|
|
4346
4681
|
if (agentToken) {
|
|
@@ -4356,7 +4691,7 @@ export class WorkflowRunner {
|
|
|
4356
4691
|
}
|
|
4357
4692
|
env.RELAYFILE_BASE_URL =
|
|
4358
4693
|
env.RELAYFILE_BASE_URL ??
|
|
4359
|
-
this.getRelayEnv()?.RELAYFILE_BASE_URL ??
|
|
4694
|
+
this.getRelayEnv(proxyMode)?.RELAYFILE_BASE_URL ??
|
|
4360
4695
|
process.env.RELAYFILE_BASE_URL ??
|
|
4361
4696
|
'http://127.0.0.1:8080';
|
|
4362
4697
|
try {
|
|
@@ -4555,6 +4890,11 @@ export class WorkflowRunner {
|
|
|
4555
4890
|
RELAY_API_KEY: this.relayApiKey ?? 'workflow-runner',
|
|
4556
4891
|
AGENT_CHANNELS: (agentChannels ?? []).join(','),
|
|
4557
4892
|
});
|
|
4893
|
+
const proxyMode = await this.resolveAgentProxyMode(agentDef, this.currentConfig);
|
|
4894
|
+
const baseEnv = this.getRelayEnv(proxyMode);
|
|
4895
|
+
const proxyEnvOverrides = proxyMode?.url && proxyMode.token
|
|
4896
|
+
? resolveProxyEnv(agentDef.cli, proxyMode.url, proxyMode.token)
|
|
4897
|
+
: undefined;
|
|
4558
4898
|
const spawnOptions = {
|
|
4559
4899
|
name: agentName,
|
|
4560
4900
|
model: agentDef.constraints?.model,
|
|
@@ -4564,6 +4904,7 @@ export class WorkflowRunner {
|
|
|
4564
4904
|
idleThresholdSecs: agentDef.constraints?.idleThresholdSecs,
|
|
4565
4905
|
cwd: agentCwd,
|
|
4566
4906
|
agentToken: this.agentTokens.get(agentDef.name),
|
|
4907
|
+
env: proxyEnvOverrides ? { ...baseEnv, ...proxyEnvOverrides } : baseEnv,
|
|
4567
4908
|
};
|
|
4568
4909
|
const sdkSpawner = getWorkflowSdkSpawner(this.relay, agentDef.cli);
|
|
4569
4910
|
if (sdkSpawner) {
|
|
@@ -4965,10 +5306,31 @@ export class WorkflowRunner {
|
|
|
4965
5306
|
}
|
|
4966
5307
|
// ── Verification ────────────────────────────────────────────────────────
|
|
4967
5308
|
runVerification(check, output, stepName, injectedTaskText, options) {
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
5309
|
+
try {
|
|
5310
|
+
const result = runVerification(check, output, stepName, injectedTaskText, { ...options, cwd: this.cwd }, {
|
|
5311
|
+
recordStepToolSideEffect: (name, effect) => this.recordStepToolSideEffect(name, effect),
|
|
5312
|
+
getOrCreateStepEvidenceRecord: (name) => this.getOrCreateStepEvidenceRecord(name),
|
|
5313
|
+
log: (message) => this.log(message),
|
|
5314
|
+
});
|
|
5315
|
+
this.updateCustomVerificationFailure(stepName, check, result.error);
|
|
5316
|
+
return result;
|
|
5317
|
+
}
|
|
5318
|
+
catch (error) {
|
|
5319
|
+
this.updateCustomVerificationFailure(stepName, check, error instanceof Error ? error.message : String(error));
|
|
5320
|
+
throw error;
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
updateCustomVerificationFailure(stepName, check, errorMessage) {
|
|
5324
|
+
if (check.type !== 'custom' || !check.value || !errorMessage) {
|
|
5325
|
+
this.lastCustomVerificationFailure.delete(stepName);
|
|
5326
|
+
return;
|
|
5327
|
+
}
|
|
5328
|
+
const marker = `custom check "${check.value}" failed\n`;
|
|
5329
|
+
const markerIndex = errorMessage.indexOf(marker);
|
|
5330
|
+
const output = markerIndex === -1 ? errorMessage.trim() : errorMessage.slice(markerIndex + marker.length).trim();
|
|
5331
|
+
this.lastCustomVerificationFailure.set(stepName, {
|
|
5332
|
+
command: check.value,
|
|
5333
|
+
output,
|
|
4972
5334
|
});
|
|
4973
5335
|
}
|
|
4974
5336
|
// ── State helpers ─────────────────────────────────────────────────────
|
|
@@ -5020,6 +5382,18 @@ export class WorkflowRunner {
|
|
|
5020
5382
|
});
|
|
5021
5383
|
if (!report)
|
|
5022
5384
|
return;
|
|
5385
|
+
const totalTokens = this.getTotalReportTokens(report);
|
|
5386
|
+
if (this.budgetTracker && report.tokens) {
|
|
5387
|
+
this.budgetTracker.recordUsage(stepName, report.tokens);
|
|
5388
|
+
this.budgetTracker.isOverBudget(stepName);
|
|
5389
|
+
const budgetStatus = this.budgetTracker.getBudgetStatus(stepName);
|
|
5390
|
+
if (budgetStatus.agentLimitExceeded) {
|
|
5391
|
+
const stepBudget = this.budgetTracker.getStepBudgetStatus(stepName);
|
|
5392
|
+
const used = stepBudget?.used?.toLocaleString('en-US') ?? totalTokens?.toLocaleString('en-US') ?? '0';
|
|
5393
|
+
const limit = stepBudget?.limit?.toLocaleString('en-US') ?? '--';
|
|
5394
|
+
this.log(`[budget] Step ${stepName} exceeded its agent budget (${used} of ${limit})`);
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5023
5397
|
this.agentReports.set(stepName, report);
|
|
5024
5398
|
this.emit({ type: 'step:agent-report', runId, stepName, report });
|
|
5025
5399
|
await this.persistAgentReport(runId, stepName, report);
|
|
@@ -5171,7 +5545,7 @@ export class WorkflowRunner {
|
|
|
5171
5545
|
console.log(chalk.dim('━'.repeat(70)));
|
|
5172
5546
|
// Always show the summary table — with agent reports when available,
|
|
5173
5547
|
// with just step/status/duration when not (non-interactive agents).
|
|
5174
|
-
console.log(formatRunSummaryTable(outcomes, this.agentReports));
|
|
5548
|
+
console.log(formatRunSummaryTable(outcomes, this.agentReports, this.budgetTracker?.getRunSummaryBudgetData()));
|
|
5175
5549
|
// Show errors and output excerpts for failed steps below the table
|
|
5176
5550
|
for (const outcome of outcomes) {
|
|
5177
5551
|
if (outcome.status !== 'failed')
|