@llm-dev-ops/agentics-cli 2.7.41 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/analytics-hub/consensus/consensus.md +47 -0
- package/agents/analytics-hub/recommendation/recommendation.md +47 -0
- package/agents/auto-optimizer/model-select/model-select.md +49 -0
- package/agents/auto-optimizer/self-optimize/self-optimize.md +44 -0
- package/agents/auto-optimizer/token/token.md +50 -0
- package/agents/benchmark-exchange/publish/publish.md +29 -0
- package/agents/config-manager/validate/validate.md +40 -0
- package/agents/connector-hub/auth-identity/auth-identity.md +29 -0
- package/agents/connector-hub/database-query/database-query.md +29 -0
- package/agents/connector-hub/erp-surface/erp-surface.md +29 -0
- package/agents/connector-hub/event-normalize/event-normalize.md +29 -0
- package/agents/connector-hub/webhook-ingest/webhook-ingest.md +29 -0
- package/agents/copilot/clarifier/clarifier.md +47 -0
- package/agents/copilot/config/config.md +37 -0
- package/agents/copilot/decomposer/decomposer.md +46 -0
- package/agents/copilot/intent/intent.md +43 -0
- package/agents/copilot/meta-reasoner/meta-reasoner.md +43 -0
- package/agents/copilot/planner/planner.md +47 -0
- package/agents/copilot/reflection/reflection.md +40 -0
- package/agents/costops/attribution/attribution.md +39 -0
- package/agents/costops/budget/budget.md +40 -0
- package/agents/costops/forecast/forecast.md +40 -0
- package/agents/costops/roi/roi.md +37 -0
- package/agents/costops/tradeoff/tradeoff.md +39 -0
- package/agents/data-vault/access-control/access-control.md +46 -0
- package/agents/data-vault/anonymize/anonymize.md +54 -0
- package/agents/edge/caching/caching.md +46 -0
- package/agents/edge/circuit-breaker/circuit-breaker.md +44 -0
- package/agents/edge/execution-guard/execution-guard.md +41 -0
- package/agents/edge/failover/failover.md +45 -0
- package/agents/edge/tool-invoke/tool-invoke.md +44 -0
- package/agents/forge/api-translation/api-translation.md +29 -0
- package/agents/forge/cli/cli.md +29 -0
- package/agents/forge/sdk/sdk.md +29 -0
- package/agents/forge/version-compat/version-compat.md +29 -0
- package/agents/governance-dashboard/audit/audit.md +39 -0
- package/agents/governance-dashboard/impact/impact.md +37 -0
- package/agents/governance-dashboard/oversight/oversight.md +41 -0
- package/agents/incident-manager/escalation/escalation.md +45 -0
- package/agents/incident-manager/hitl/hitl.md +46 -0
- package/agents/incident-manager/post-mortem/post-mortem.md +52 -0
- package/agents/inference-gateway/route/route.md +29 -0
- package/agents/latency-lens/cold-start/cold-start.md +29 -0
- package/agents/latency-lens/latency/latency.md +29 -0
- package/agents/marketplace/deprecation/deprecation.md +29 -0
- package/agents/marketplace/package/package.md +29 -0
- package/agents/memory-graph/conversation/conversation.md +37 -0
- package/agents/memory-graph/decisions/decisions.md +45 -0
- package/agents/memory-graph/knowledge-graph/knowledge-graph.md +46 -0
- package/agents/memory-graph/lineage/lineage.md +37 -0
- package/agents/memory-graph/patterns/patterns.md +45 -0
- package/agents/memory-graph/retrieval/retrieval.md +43 -0
- package/agents/observatory/failures/failures.md +29 -0
- package/agents/observatory/health-check/health-check.md +29 -0
- package/agents/observatory/post-mortem/post-mortem.md +29 -0
- package/agents/observatory/slo/slo.md +29 -0
- package/agents/observatory/telemetry/telemetry.md +29 -0
- package/agents/observatory/usage-patterns/usage-patterns.md +29 -0
- package/agents/observatory/visualization/visualization.md +29 -0
- package/agents/orchestrator/dependencies/dependencies.md +40 -0
- package/agents/orchestrator/parallel/parallel.md +43 -0
- package/agents/orchestrator/retry/retry.md +45 -0
- package/agents/orchestrator/scheduler/scheduler.md +44 -0
- package/agents/orchestrator/state-machine/state-machine.md +53 -0
- package/agents/orchestrator/swarm/swarm.md +44 -0
- package/agents/orchestrator/workflow/workflow.md +48 -0
- package/agents/platform/decision/decision.md +40 -0
- package/agents/platform/decision-memo/decision-memo.md +69 -0
- package/agents/platform/executive-summary/executive-summary.md +44 -0
- package/agents/platform/risk-score/risk-score.md +50 -0
- package/agents/policy-engine/approval/approval.md +40 -0
- package/agents/policy-engine/constraints/constraints.md +38 -0
- package/agents/policy-engine/enforce/enforce.md +39 -0
- package/agents/registry/bootstrap/bootstrap.md +29 -0
- package/agents/registry/index/index.md +29 -0
- package/agents/registry/reputation/reputation.md +29 -0
- package/agents/research-lab/hypothesis/hypothesis.md +50 -0
- package/agents/research-lab/metrics/metrics.md +50 -0
- package/agents/schema-registry/validate/validate.md +37 -0
- package/agents/sentinel/alert/alert.md +29 -0
- package/agents/sentinel/anomaly/anomaly.md +29 -0
- package/agents/sentinel/correlation/correlation.md +29 -0
- package/agents/sentinel/drift/drift.md +29 -0
- package/agents/sentinel/rca/rca.md +29 -0
- package/agents/shield/abuse/abuse.md +29 -0
- package/agents/shield/credential-exposure/credential-exposure.md +29 -0
- package/agents/shield/moderation/moderation.md +29 -0
- package/agents/shield/pii/pii.md +29 -0
- package/agents/shield/prompt-injection/prompt-injection.md +29 -0
- package/agents/shield/redaction/redaction.md +29 -0
- package/agents/shield/safety-boundary/safety-boundary.md +29 -0
- package/agents/shield/secrets/secrets.md +29 -0
- package/agents/shield/toxicity/toxicity.md +29 -0
- package/agents/simulator/scenario/scenario.md +53 -0
- package/agents/simulator/what-if/what-if.md +52 -0
- package/agents/test-bench/adversarial/adversarial.md +33 -0
- package/agents/test-bench/benchmark/benchmark.md +34 -0
- package/agents/test-bench/bias/bias.md +33 -0
- package/agents/test-bench/compare/compare.md +33 -0
- package/agents/test-bench/consistency/consistency.md +33 -0
- package/agents/test-bench/faithfulness/faithfulness.md +34 -0
- package/agents/test-bench/golden-dataset/golden-dataset.md +33 -0
- package/agents/test-bench/hallucination/hallucination.md +34 -0
- package/agents/test-bench/prompt-sensitivity/prompt-sensitivity.md +33 -0
- package/agents/test-bench/quality/quality.md +33 -0
- package/agents/test-bench/red-team/red-team.md +33 -0
- package/agents/test-bench/regression/regression.md +33 -0
- package/agents/test-bench/stress/stress.md +34 -0
- package/agents/test-bench/synthetic-data/synthetic-data.md +35 -0
- package/dist/agents/agent-prompts.d.ts +77 -0
- package/dist/agents/agent-prompts.d.ts.map +1 -0
- package/dist/agents/agent-prompts.js +230 -0
- package/dist/agents/agent-prompts.js.map +1 -0
- package/dist/agents/repo-agent-runner.d.ts +1 -0
- package/dist/agents/repo-agent-runner.d.ts.map +1 -1
- package/dist/agents/repo-agent-runner.js +67 -35
- package/dist/agents/repo-agent-runner.js.map +1 -1
- package/dist/cli/index.js +22 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/agents.d.ts +19 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +344 -144
- package/dist/commands/agents.js.map +1 -1
- package/dist/pipeline/auto-chain.d.ts.map +1 -1
- package/dist/pipeline/auto-chain.js +18 -7
- package/dist/pipeline/auto-chain.js.map +1 -1
- package/dist/pipeline/fleet-dispatch-bounds.d.ts +115 -0
- package/dist/pipeline/fleet-dispatch-bounds.d.ts.map +1 -0
- package/dist/pipeline/fleet-dispatch-bounds.js +190 -0
- package/dist/pipeline/fleet-dispatch-bounds.js.map +1 -0
- package/dist/pipeline/output-validator.d.ts +7 -2
- package/dist/pipeline/output-validator.d.ts.map +1 -1
- package/dist/pipeline/output-validator.js +61 -4
- package/dist/pipeline/output-validator.js.map +1 -1
- package/dist/pipeline/phase2/phases/adr-generator.d.ts.map +1 -1
- package/dist/pipeline/phase2/phases/adr-generator.js +6 -5
- package/dist/pipeline/phase2/phases/adr-generator.js.map +1 -1
- package/dist/pipeline/phase3/phases/domain-codegen.d.ts.map +1 -1
- package/dist/pipeline/phase3/phases/domain-codegen.js +8 -2
- package/dist/pipeline/phase3/phases/domain-codegen.js.map +1 -1
- package/dist/pipeline/phase4/phases/schema-generator.js +3 -3
- package/dist/pipeline/phase4/phases/schema-generator.js.map +1 -1
- package/dist/pipeline/phase7/coherence-gate.d.ts.map +1 -1
- package/dist/pipeline/phase7/coherence-gate.js +8 -24
- package/dist/pipeline/phase7/coherence-gate.js.map +1 -1
- package/dist/pipeline/phase7/coordinator.d.ts +34 -0
- package/dist/pipeline/phase7/coordinator.d.ts.map +1 -1
- package/dist/pipeline/phase7/coordinator.js +131 -71
- package/dist/pipeline/phase7/coordinator.js.map +1 -1
- package/dist/pipeline/phase7/field-mappers.d.ts +43 -0
- package/dist/pipeline/phase7/field-mappers.d.ts.map +1 -0
- package/dist/pipeline/phase7/field-mappers.js +278 -0
- package/dist/pipeline/phase7/field-mappers.js.map +1 -0
- package/dist/pipeline/phase7/field-writer.d.ts +53 -0
- package/dist/pipeline/phase7/field-writer.d.ts.map +1 -0
- package/dist/pipeline/phase7/field-writer.js +178 -0
- package/dist/pipeline/phase7/field-writer.js.map +1 -0
- package/dist/pipeline/phase7/writer-agent.d.ts +83 -0
- package/dist/pipeline/phase7/writer-agent.d.ts.map +1 -0
- package/dist/pipeline/phase7/writer-agent.js +174 -0
- package/dist/pipeline/phase7/writer-agent.js.map +1 -0
- package/dist/pipeline/ruflo-phase-executor.d.ts.map +1 -1
- package/dist/pipeline/ruflo-phase-executor.js +49 -23
- package/dist/pipeline/ruflo-phase-executor.js.map +1 -1
- package/dist/pipeline/swarm-orchestrator.d.ts +3 -41
- package/dist/pipeline/swarm-orchestrator.d.ts.map +1 -1
- package/dist/pipeline/swarm-orchestrator.js +9 -75
- package/dist/pipeline/swarm-orchestrator.js.map +1 -1
- package/dist/synthesis/fcv-coherence.d.ts +24 -0
- package/dist/synthesis/fcv-coherence.d.ts.map +1 -0
- package/dist/synthesis/fcv-coherence.js +36 -0
- package/dist/synthesis/fcv-coherence.js.map +1 -0
- package/dist/synthesis/financial-claim-extractor.d.ts +8 -0
- package/dist/synthesis/financial-claim-extractor.d.ts.map +1 -1
- package/dist/synthesis/financial-claim-extractor.js +74 -1
- package/dist/synthesis/financial-claim-extractor.js.map +1 -1
- package/dist/synthesis/financial-consistency-rules.d.ts.map +1 -1
- package/dist/synthesis/financial-consistency-rules.js +21 -12
- package/dist/synthesis/financial-consistency-rules.js.map +1 -1
- package/dist/synthesis/financial-consistency-runner.d.ts +12 -0
- package/dist/synthesis/financial-consistency-runner.d.ts.map +1 -1
- package/dist/synthesis/financial-consistency-runner.js +25 -3
- package/dist/synthesis/financial-consistency-runner.js.map +1 -1
- package/dist/synthesis/simulation-artifact-generator.d.ts.map +1 -1
- package/dist/synthesis/simulation-artifact-generator.js +5 -0
- package/dist/synthesis/simulation-artifact-generator.js.map +1 -1
- package/dist/synthesis/simulation-renderers.d.ts +2 -0
- package/dist/synthesis/simulation-renderers.d.ts.map +1 -1
- package/dist/synthesis/simulation-renderers.js +8 -8
- package/dist/synthesis/simulation-renderers.js.map +1 -1
- package/dist/synthesis/unit-economics-loader.d.ts +7 -0
- package/dist/synthesis/unit-economics-loader.d.ts.map +1 -1
- package/dist/synthesis/unit-economics-loader.js +11 -2
- package/dist/synthesis/unit-economics-loader.js.map +1 -1
- package/package.json +8 -7
package/dist/commands/agents.js
CHANGED
|
@@ -13,10 +13,186 @@ import { loadEndpointConfig } from '../config/endpoints.js';
|
|
|
13
13
|
import { createAdapter } from '../adapters/base-adapter.js';
|
|
14
14
|
import { getAnthropicApiKey } from '../utils/credentials.js';
|
|
15
15
|
import { executeSimulateCommand } from './simulate.js';
|
|
16
|
-
import {
|
|
16
|
+
import { execFile } from 'node:child_process';
|
|
17
|
+
import { promisify } from 'node:util';
|
|
18
|
+
import { writeFileSync as fsWriteFileSync, existsSync as fsExistsSync, readFileSync as fsReadFileSync } from 'node:fs';
|
|
19
|
+
import { join as pathJoin } from 'node:path';
|
|
17
20
|
import { isTransientFailure, isTerminalFailure } from '../errors/transient.js';
|
|
18
21
|
import { recordDegradation, drainDegradations } from '../observability/degradations.js';
|
|
19
22
|
import { computePhase1Verdict } from '../pipeline/phase1-verdict.js';
|
|
23
|
+
// ADR-PIPELINE-108: shared dispatch bounds (per-agent timeout + cloud breaker).
|
|
24
|
+
import { CloudCircuitBreaker, resolveFleetDispatchConfig, withAgentTimeout, AGENT_TIMED_OUT, raceFleetDeadline, decideAgentTier, } from '../pipeline/fleet-dispatch-bounds.js';
|
|
25
|
+
// ADR-PIPELINE-109: repo-backed agent execution (Tier 1).
|
|
26
|
+
import { isRepoAgentMode, hasRepoAgent, isDomainSourceCached, executeRepoAgent, } from '../agents/repo-agent-runner.js';
|
|
27
|
+
// Per-agent sub-prompt wiring: inject each agent's hand-authored guidance
|
|
28
|
+
// (agents/<domain>/<agent>/<agent>.md) + use case + prior run outputs into the
|
|
29
|
+
// payload so it reaches the repo handler's callClaude prompt and the cloud tier.
|
|
30
|
+
import { composeAgentExecutionContext, injectGuidanceIntoPayload, extractUseCase, getPriorAgentOutputs, recordAgentOutput, resetAgentRunContext, summarizeAgentResponse, } from '../agents/agent-prompts.js';
|
|
31
|
+
const execFileAsync = promisify(execFile);
|
|
32
|
+
/**
|
|
33
|
+
* ADR-PIPELINE-108 D3: resolve the `claude` binary once and memoize it.
|
|
34
|
+
* Avoids spawning `which`/`where` on every one of the ~109 fleet agents.
|
|
35
|
+
* Returns null when no claude binary is found (caller falls back to cloud).
|
|
36
|
+
*/
|
|
37
|
+
// ADR-PIPELINE-108 D3: fully async (no execFileSync on the fleet path) and
|
|
38
|
+
// promise-memoized, so concurrent fleet agents trigger at most ONE `which`
|
|
39
|
+
// resolution and the event loop is never blocked.
|
|
40
|
+
let _claudeBinPromise;
|
|
41
|
+
function resolveClaudeBin() {
|
|
42
|
+
if (_claudeBinPromise)
|
|
43
|
+
return _claudeBinPromise;
|
|
44
|
+
_claudeBinPromise = (async () => {
|
|
45
|
+
const envBin = process.env['AGENTICS_CLAUDE_BIN'];
|
|
46
|
+
if (envBin)
|
|
47
|
+
return envBin;
|
|
48
|
+
for (const candidate of ['claude', 'claude-code']) {
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await execFileAsync(process.platform === 'win32' ? 'where' : 'which', [candidate], { encoding: 'utf-8', timeout: 5_000 });
|
|
51
|
+
const found = stdout.trim().split(/\r?\n/)[0]?.trim();
|
|
52
|
+
if (found)
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
catch { /* not found — try next candidate */ }
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
})();
|
|
59
|
+
return _claudeBinPromise;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* ADR-PIPELINE-108 D4: agents with a LOCAL execution path (claude --print) do
|
|
63
|
+
* not need a healthy cloud backend, so they are exempt from the preflight
|
|
64
|
+
* cloud-health skip and from breaker accounting. (copilot/* and
|
|
65
|
+
* inference-gateway/route run locally via Claude Max — ADR-066.)
|
|
66
|
+
*/
|
|
67
|
+
function isLocallyServable(domain, agent) {
|
|
68
|
+
if (domain === 'copilot')
|
|
69
|
+
return true;
|
|
70
|
+
if (domain === 'inference-gateway' && agent === 'route')
|
|
71
|
+
return true;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* ADR-PIPELINE-108 D1/D2: invoke ONE fleet agent with a per-agent timeout and
|
|
76
|
+
* cloud circuit breaker. NEVER rejects — always resolves to a result object so
|
|
77
|
+
* the fleet's allSettled / partial-snapshot logic stays clean.
|
|
78
|
+
*/
|
|
79
|
+
async function boundedFleetInvoke(domain, agent, query, options, correlationId, breaker, perAgentMs) {
|
|
80
|
+
const id = `${domain}/${agent}`;
|
|
81
|
+
const local = isLocallyServable(domain, agent);
|
|
82
|
+
// D2: once the breaker has tripped, skip remaining cloud agents instead of
|
|
83
|
+
// each eating its full per-agent timeout. Local-capable agents still run.
|
|
84
|
+
if (!local && breaker.shouldSkip()) {
|
|
85
|
+
return { kind: 'agent', domain, agent, status: 0, response: { _breaker_skipped: true, reason: 'cloud-breaker-tripped' } };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const payload = buildDomainPayload(domain, agent, query, correlationId);
|
|
89
|
+
// D1: race the invocation against the per-agent deadline.
|
|
90
|
+
const raced = await withAgentTimeout(executeAgentsInvokeCommand(domain, agent, payload, options), perAgentMs);
|
|
91
|
+
if (raced === AGENT_TIMED_OUT) {
|
|
92
|
+
if (!local)
|
|
93
|
+
breaker.record(true, 'cloud', id);
|
|
94
|
+
return { kind: 'agent', domain, agent, status: 504, response: { _timeout: true, error: `agent ${id} timed out after ${perAgentMs}ms` } };
|
|
95
|
+
}
|
|
96
|
+
const result = raced; // narrowed to AgentsInvokeResult after the sentinel check
|
|
97
|
+
const respObj = result.response && typeof result.response === 'object' ? result.response : undefined;
|
|
98
|
+
const isFallback = respObj?.['_fallback'] === true;
|
|
99
|
+
const failed = (result.status ?? 200) >= 400;
|
|
100
|
+
if (!local)
|
|
101
|
+
breaker.record(failed, isFallback ? 'local' : 'cloud', id);
|
|
102
|
+
// ADR-PIPELINE-109 D5: carry the execution tier that served this agent.
|
|
103
|
+
const tier = classifyExecutionTier(result);
|
|
104
|
+
// Record this agent's output into the run-scoped accumulator so later agents
|
|
105
|
+
// dispatched in this run can review what was already produced.
|
|
106
|
+
recordAgentOutput({
|
|
107
|
+
domain,
|
|
108
|
+
agent,
|
|
109
|
+
status: result.status ?? 200,
|
|
110
|
+
tier,
|
|
111
|
+
summary: summarizeAgentResponse(result.response),
|
|
112
|
+
});
|
|
113
|
+
return { kind: 'agent', domain, agent, status: result.status ?? 200, response: result.response, execution_tier: tier };
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (!local)
|
|
117
|
+
breaker.record(true, 'cloud', id);
|
|
118
|
+
return { kind: 'agent', domain, agent, status: 502, response: { error: err instanceof Error ? err.message : String(err) } };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* ADR-PIPELINE-109 D5: one-line provenance summary — how many fleet agents were
|
|
123
|
+
* served by each tier. `unavailable` = breaker-skipped / timed-out / errored
|
|
124
|
+
* (no content), which carry no tier.
|
|
125
|
+
*/
|
|
126
|
+
function summarizeTiers(results) {
|
|
127
|
+
let repo = 0, cloud = 0, synthetic = 0, unavailable = 0;
|
|
128
|
+
for (const r of results) {
|
|
129
|
+
if (r.execution_tier === 'repo')
|
|
130
|
+
repo++;
|
|
131
|
+
else if (r.execution_tier === 'cloud')
|
|
132
|
+
cloud++;
|
|
133
|
+
else if (r.execution_tier === 'synthetic')
|
|
134
|
+
synthetic++;
|
|
135
|
+
else
|
|
136
|
+
unavailable++;
|
|
137
|
+
}
|
|
138
|
+
return `${repo} repo · ${cloud} cloud · ${synthetic} synthetic` + (unavailable ? ` · ${unavailable} unavailable` : '');
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* ADR-PIPELINE-108 D4: probe fleet backends and return the set of
|
|
142
|
+
* `domain/agent` ids to skip (unhealthy cloud backend, no local path). Never
|
|
143
|
+
* throws — a probe failure returns an empty skip set (dispatch everything).
|
|
144
|
+
* Does NOT abort the fleet (unlike the Phase 2 preflight's abort verdict):
|
|
145
|
+
* Phase 1 is the entry point, so we degrade rather than halt.
|
|
146
|
+
*/
|
|
147
|
+
async function fleetPreflightSkipSet(refs, traceId) {
|
|
148
|
+
try {
|
|
149
|
+
const { runPhase2Preflight } = await import('../pipeline/phase2-preflight.js');
|
|
150
|
+
const cloudRefs = refs
|
|
151
|
+
// ADR-PIPELINE-109 D5 ↔ ADR-108 D4: agents served locally (claude path) or
|
|
152
|
+
// by their repo tier do not need a healthy cloud backend — don't probe them.
|
|
153
|
+
.filter((r) => !isLocallyServable(r.domain, r.agent) && resolveAgentTier(r.domain) !== 'repo')
|
|
154
|
+
.map((r) => ({ domain: r.domain, agent: r.agent, service: `${r.domain}-agents` }));
|
|
155
|
+
if (cloudRefs.length === 0)
|
|
156
|
+
return new Set();
|
|
157
|
+
const pf = await runPhase2Preflight(cloudRefs, traceId);
|
|
158
|
+
console.error(` [PREFLIGHT] ${pf.healthy.length}/${pf.probed} fleet backends healthy in ${pf.durationMs}ms (verdict: ${pf.verdict})`);
|
|
159
|
+
if (pf.skipped.length > 0) {
|
|
160
|
+
console.error(` [PREFLIGHT] Skipping ${pf.skipped.length} agents on unhealthy backends: ${pf.unhealthy.join(', ')}`);
|
|
161
|
+
}
|
|
162
|
+
return new Set(pf.skipped);
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.error(` [PREFLIGHT] probe skipped (error: ${err instanceof Error ? err.message : String(err)}) — dispatching full fleet`);
|
|
166
|
+
return new Set();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* ADR-PIPELINE-108 D5: persist the effective fleet-dispatch config + Phase 1
|
|
171
|
+
* verdict. Writes a DEDICATED `fleet-dispatch.json` artifact (which later
|
|
172
|
+
* pipeline phases never rewrite — unlike the shared `manifest.json`, which
|
|
173
|
+
* Phases 2–7 overwrite) and ALSO best-effort patches `manifest.json`. Never
|
|
174
|
+
* throws — observability must not fail the pipeline.
|
|
175
|
+
*/
|
|
176
|
+
function persistFleetDispatch(runDir, record, verdict) {
|
|
177
|
+
try {
|
|
178
|
+
// 1) Dedicated, stable artifact — the authoritative D5 record. Written
|
|
179
|
+
// SYNCHRONOUSLY (no await) so no event-loop yield can interleave or be
|
|
180
|
+
// interrupted before the write lands.
|
|
181
|
+
fsWriteFileSync(pathJoin(runDir, 'fleet-dispatch.json'), JSON.stringify(record, null, 2), 'utf-8');
|
|
182
|
+
// 2) Best-effort manifest patch (may be overwritten by later phases).
|
|
183
|
+
const mPath = pathJoin(runDir, 'manifest.json');
|
|
184
|
+
if (fsExistsSync(mPath)) {
|
|
185
|
+
const m = JSON.parse(fsReadFileSync(mPath, 'utf-8'));
|
|
186
|
+
m['phase1_verdict'] = verdict.verdict;
|
|
187
|
+
m['phase1_reason'] = verdict.reason;
|
|
188
|
+
m['phase1_degradations'] = verdict.degradations;
|
|
189
|
+
m['phase1_stats'] = verdict.stats;
|
|
190
|
+
m['fleet_dispatch'] = record;
|
|
191
|
+
fsWriteFileSync(mPath, JSON.stringify(m, null, 2), 'utf-8');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch { /* non-fatal */ }
|
|
195
|
+
}
|
|
20
196
|
// ============================================================================
|
|
21
197
|
// ADR-066: Copilot agents via claude --print (Claude Max — no API key needed)
|
|
22
198
|
// ============================================================================
|
|
@@ -30,31 +206,17 @@ const COPILOT_PROMPTS = {
|
|
|
30
206
|
'meta-reasoner': 'You are a meta-reasoning agent. Analyze the reasoning traces and identify optimization opportunities. Return JSON: { insights: [], optimizations: [], confidence: number }',
|
|
31
207
|
};
|
|
32
208
|
async function runCopilotViaClaudeMax(agent, payload, correlationId) {
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
const envBin = process.env['AGENTICS_CLAUDE_BIN'];
|
|
36
|
-
if (envBin) {
|
|
37
|
-
claudeBin = envBin;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
for (const candidate of ['claude', 'claude-code']) {
|
|
41
|
-
try {
|
|
42
|
-
const found = execFileSync(process.platform === 'win32' ? 'where' : 'which', [candidate], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 5_000, encoding: 'utf-8' }).trim().split(/\r?\n/)[0]?.trim();
|
|
43
|
-
if (found) {
|
|
44
|
-
claudeBin = found;
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch { /* not found */ }
|
|
49
|
-
}
|
|
50
|
-
}
|
|
209
|
+
// ADR-PIPELINE-108 D3: memoized binary resolution (no per-agent `which`).
|
|
210
|
+
const claudeBin = await resolveClaudeBin();
|
|
51
211
|
if (!claudeBin)
|
|
52
212
|
return null; // claude not available — fall through to cloud function
|
|
53
213
|
const systemPrompt = COPILOT_PROMPTS[agent] ?? `You are a ${agent} agent. Analyze the following and return structured JSON.`;
|
|
54
214
|
const query = String(payload['objective'] ?? payload['text'] ?? payload['query'] ?? JSON.stringify(payload));
|
|
55
215
|
const fullPrompt = `${systemPrompt}\n\nInput:\n${query}\n\nReturn ONLY valid JSON, no markdown fences.`;
|
|
56
216
|
try {
|
|
57
|
-
|
|
217
|
+
// ADR-PIPELINE-108 D3: async exec — does NOT block the event loop, so the
|
|
218
|
+
// ~109-agent fleet runs concurrently instead of serializing behind this call.
|
|
219
|
+
const { stdout: rawOutput } = await execFileAsync(claudeBin, [
|
|
58
220
|
'--print',
|
|
59
221
|
'--output-format', 'text',
|
|
60
222
|
'--model', process.env['AGENTICS_CLAUDE_MODEL'] || 'claude-sonnet-4-20250514',
|
|
@@ -63,7 +225,6 @@ async function runCopilotViaClaudeMax(agent, payload, correlationId) {
|
|
|
63
225
|
encoding: 'utf-8',
|
|
64
226
|
timeout: 60_000,
|
|
65
227
|
maxBuffer: 5 * 1024 * 1024,
|
|
66
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
228
|
env: { ...process.env, MCP_SERVER_MODE: undefined },
|
|
68
229
|
});
|
|
69
230
|
// Parse JSON from output
|
|
@@ -102,23 +263,8 @@ async function runCopilotViaClaudeMax(agent, payload, correlationId) {
|
|
|
102
263
|
// for a request given hints). We run it locally through the user's Claude Max
|
|
103
264
|
// session so no server-side Anthropic API key is required.
|
|
104
265
|
async function runInferenceRoutingViaClaudeMax(payload, correlationId) {
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
if (envBin) {
|
|
108
|
-
claudeBin = envBin;
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
for (const candidate of ['claude', 'claude-code']) {
|
|
112
|
-
try {
|
|
113
|
-
const found = execFileSync(process.platform === 'win32' ? 'where' : 'which', [candidate], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 5_000, encoding: 'utf-8' }).trim().split(/\r?\n/)[0]?.trim();
|
|
114
|
-
if (found) {
|
|
115
|
-
claudeBin = found;
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch { /* not found */ }
|
|
120
|
-
}
|
|
121
|
-
}
|
|
266
|
+
// ADR-PIPELINE-108 D3: memoized binary resolution (no per-agent `which`).
|
|
267
|
+
const claudeBin = await resolveClaudeBin();
|
|
122
268
|
if (!claudeBin)
|
|
123
269
|
return null;
|
|
124
270
|
const systemPrompt = [
|
|
@@ -144,7 +290,8 @@ async function runInferenceRoutingViaClaudeMax(payload, correlationId) {
|
|
|
144
290
|
].join('\n');
|
|
145
291
|
const fullPrompt = `${systemPrompt}\n\nRequest:\n${JSON.stringify(payload, null, 2)}`;
|
|
146
292
|
try {
|
|
147
|
-
|
|
293
|
+
// ADR-PIPELINE-108 D3: async exec — non-blocking.
|
|
294
|
+
const { stdout: rawOutput } = await execFileAsync(claudeBin, [
|
|
148
295
|
'--print',
|
|
149
296
|
'--output-format', 'text',
|
|
150
297
|
'--model', process.env['AGENTICS_CLAUDE_MODEL'] || 'claude-sonnet-4-20250514',
|
|
@@ -153,7 +300,6 @@ async function runInferenceRoutingViaClaudeMax(payload, correlationId) {
|
|
|
153
300
|
encoding: 'utf-8',
|
|
154
301
|
timeout: 60_000,
|
|
155
302
|
maxBuffer: 5 * 1024 * 1024,
|
|
156
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
157
303
|
env: { ...process.env, MCP_SERVER_MODE: undefined },
|
|
158
304
|
});
|
|
159
305
|
let cleaned = rawOutput.trim();
|
|
@@ -334,6 +480,38 @@ export const RESOLUTION_ONLY_DOMAINS = new Set([
|
|
|
334
480
|
export function isResolutionOnlyDomain(domain) {
|
|
335
481
|
return RESOLUTION_ONLY_DOMAINS.has(domain);
|
|
336
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* ADR-PIPELINE-109 D5: classify how a result was produced, for provenance.
|
|
485
|
+
* `repo` is set explicitly by the Tier-1 path; otherwise a `_fallback` marker
|
|
486
|
+
* means a locally-computed synthetic stub, and anything else is a real cloud
|
|
487
|
+
* (or local-claude, ADR-066) response.
|
|
488
|
+
*/
|
|
489
|
+
export function classifyExecutionTier(result) {
|
|
490
|
+
if (result.execution_tier)
|
|
491
|
+
return result.execution_tier;
|
|
492
|
+
const resp = result.response;
|
|
493
|
+
if (resp && typeof resp === 'object' && resp['_fallback'] === true) {
|
|
494
|
+
return 'synthetic';
|
|
495
|
+
}
|
|
496
|
+
return 'cloud';
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* ADR-PIPELINE-109 D3/D4: decide whether to route an agent to the repo tier or
|
|
500
|
+
* the cloud tier. `AGENTICS_AGENT_TIER` forces a tier; the default `auto`
|
|
501
|
+
* prefers repo when the domain is repo-eligible (claude available + a repo
|
|
502
|
+
* mapping + source actually cached — D2), else cloud. A `forced` repo tier
|
|
503
|
+
* skips the source-cache gate so `executeRepoAgent` can provision on demand.
|
|
504
|
+
*/
|
|
505
|
+
export function resolveAgentTier(domain) {
|
|
506
|
+
// Pure decision lives in fleet-dispatch-bounds (unit-testable); here we feed
|
|
507
|
+
// it the live predicates from the repo-agent runtime.
|
|
508
|
+
return decideAgentTier({
|
|
509
|
+
forced: process.env['AGENTICS_AGENT_TIER'],
|
|
510
|
+
hasRepo: hasRepoAgent(domain),
|
|
511
|
+
repoMode: isRepoAgentMode(),
|
|
512
|
+
sourceCached: isDomainSourceCached(domain),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
337
515
|
// ============================================================================
|
|
338
516
|
// Command Implementations
|
|
339
517
|
// ============================================================================
|
|
@@ -416,7 +594,43 @@ export async function executeAgentsInvokeCommand(domain, agent, payload, options
|
|
|
416
594
|
}
|
|
417
595
|
}
|
|
418
596
|
}
|
|
419
|
-
//
|
|
597
|
+
// Per-agent sub-prompt wiring: prepend the agent's hand-authored guidance
|
|
598
|
+
// (agents/<domain>/<agent>/<agent>.md), the run's use case, and any outputs
|
|
599
|
+
// already produced earlier in this run onto the payload text. This reaches the
|
|
600
|
+
// repo handler's callClaude userMessage (Tier 1) and persists into the cloud
|
|
601
|
+
// fallback payload (Tier 2), so the sub-prompt's "execute your repo code,
|
|
602
|
+
// review the use case, review prior outputs" instruction is actually applied.
|
|
603
|
+
if (normalizedPayload && typeof normalizedPayload === 'object') {
|
|
604
|
+
const payloadObj = normalizedPayload;
|
|
605
|
+
const guidance = composeAgentExecutionContext({
|
|
606
|
+
domain,
|
|
607
|
+
agent,
|
|
608
|
+
useCase: extractUseCase(payloadObj),
|
|
609
|
+
priorOutputs: getPriorAgentOutputs(),
|
|
610
|
+
});
|
|
611
|
+
if (guidance) {
|
|
612
|
+
normalizedPayload = injectGuidanceIntoPayload(payloadObj, guidance);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// ADR-PIPELINE-109 D3/D4: Tier 1 — run the agent's REAL repo code (its actual
|
|
616
|
+
// handler + system prompts) via claude --print. Chosen when the domain is
|
|
617
|
+
// repo-eligible (D2 cache gate) or forced via AGENTICS_AGENT_TIER=repo. On any
|
|
618
|
+
// non-2xx (auth/provision/handler failure) we degrade to the cloud tier (D6),
|
|
619
|
+
// visibly — never silently presenting a synthetic stub as repo output.
|
|
620
|
+
if (resolveAgentTier(domain) === 'repo') {
|
|
621
|
+
const repoResult = await executeRepoAgent(domain, agent, normalizedPayload, correlationId);
|
|
622
|
+
if (repoResult.status >= 200 && repoResult.status < 300) {
|
|
623
|
+
// Preserve the tier executeRepoAgent set: 'repo' only for a genuine
|
|
624
|
+
// handler run; its internal system-prompt / rust-native fallbacks
|
|
625
|
+
// self-tag 'synthetic' so provenance is not inflated (ADR-109 D5).
|
|
626
|
+
return { ...repoResult, execution_tier: repoResult.execution_tier ?? 'repo' };
|
|
627
|
+
}
|
|
628
|
+
const reason = (repoResult.response && typeof repoResult.response === 'object'
|
|
629
|
+
? repoResult.response['error']
|
|
630
|
+
: undefined) ?? `status ${repoResult.status}`;
|
|
631
|
+
console.error(` [TIER] ${domain}/${agent} repo tier unavailable (${String(reason).slice(0, 80)}) — degrading to cloud`);
|
|
632
|
+
}
|
|
633
|
+
// Tier 2: Cloud Run services (Tier 3 synthetic fallback computed on failure).
|
|
420
634
|
const config = loadEndpointConfig(domainConfig.serviceName);
|
|
421
635
|
const adapter = createAdapter(domainConfig.serviceName, config, correlationId);
|
|
422
636
|
// ADR-066: Copilot agents run via claude --print (Claude Max) — no API key needed
|
|
@@ -3845,40 +4059,36 @@ export async function executeNaturalLanguageRoute(query, options) {
|
|
|
3845
4059
|
console.error(` [RUVECTOR] Simulation failed after ${MAX_RETRIES + 1} attempts: ${lastErr?.message ?? 'unknown error'}`);
|
|
3846
4060
|
return { kind: 'simulation', domain: 'simulator', agent: 'enterprise', status: 502, response: { error: lastErr?.message ?? 'Enterprise simulation failed after retries' } };
|
|
3847
4061
|
})();
|
|
3848
|
-
//
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
const result = await executeAgentsInvokeCommand(ref.domain, ref.agent, payload, options);
|
|
3867
|
-
return { kind: 'agent', domain: ref.domain, agent: ref.agent, status: result.status ?? 200, response: result.response };
|
|
3868
|
-
}
|
|
3869
|
-
catch (err) {
|
|
3870
|
-
return { kind: 'agent', domain: ref.domain, agent: ref.agent, status: 502, response: { error: err instanceof Error ? err.message : String(err) } };
|
|
3871
|
-
}
|
|
3872
|
-
})());
|
|
3873
|
-
}
|
|
3874
|
-
const allResults = await Promise.allSettled([Promise.resolve(simResult), ...agentPromises]);
|
|
4062
|
+
// ADR-PIPELINE-108 D1/D2/D4 — bounded, non-blocking graph-routed dispatch
|
|
4063
|
+
// (same discipline as the full-fleet path: per-agent timeout + breaker +
|
|
4064
|
+
// preflight skip; no racy per-agent process.env toggling).
|
|
4065
|
+
// Start each `agentics ask` run with an empty prior-output accumulator so
|
|
4066
|
+
// agents only see outputs from THIS run (matters in the long-lived MCP
|
|
4067
|
+
// server; harmless in the short-lived CLI process).
|
|
4068
|
+
resetAgentRunContext();
|
|
4069
|
+
const fleetCfg = resolveFleetDispatchConfig();
|
|
4070
|
+
const fleetBreaker = new CloudCircuitBreaker(fleetCfg.cloud_breaker_threshold);
|
|
4071
|
+
const fleetSkip = await fleetPreflightSkipSet(graphResult.agents, correlationId);
|
|
4072
|
+
const dispatchAgents = graphResult.agents.filter((a) => !fleetSkip.has(`${a.domain}/${a.agent}`));
|
|
4073
|
+
const fleetPreflightSkipped = graphResult.agents.length - dispatchAgents.length;
|
|
4074
|
+
console.error(` Dispatching ${dispatchAgents.length} graph-routed agents (preflight-skipped ${fleetPreflightSkipped})...`);
|
|
4075
|
+
const completedFleet = [];
|
|
4076
|
+
const agentPromises = dispatchAgents.map((ref) => boundedFleetInvoke(ref.domain, ref.agent, query, options, correlationId, fleetBreaker, fleetCfg.agent_timeout_ms)
|
|
4077
|
+
.then((r) => { completedFleet.push(r); return r; }));
|
|
4078
|
+
// D2: fleet-wide deadline.
|
|
4079
|
+
const settledOrTimeout = await raceFleetDeadline(Promise.allSettled([Promise.resolve(simResult), ...agentPromises]), fleetCfg.fleet_timeout_ms);
|
|
3875
4080
|
const elapsed = Date.now() - start;
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
4081
|
+
console.error(` [TIER] execution: ${summarizeTiers(completedFleet)}`);
|
|
4082
|
+
let allResults;
|
|
4083
|
+
if (settledOrTimeout === 'TIMEOUT') {
|
|
4084
|
+
allResults = [
|
|
4085
|
+
{ status: 'fulfilled', value: simResult },
|
|
4086
|
+
...completedFleet.map((v) => ({ status: 'fulfilled', value: v })),
|
|
4087
|
+
];
|
|
4088
|
+
console.error(` [FLEET] deadline ${fleetCfg.fleet_timeout_ms}ms hit — ${completedFleet.length}/${dispatchAgents.length} agents completed`);
|
|
3879
4089
|
}
|
|
3880
4090
|
else {
|
|
3881
|
-
|
|
4091
|
+
allResults = settledOrTimeout;
|
|
3882
4092
|
}
|
|
3883
4093
|
// Build multi-result response
|
|
3884
4094
|
const intents = [];
|
|
@@ -3927,7 +4137,7 @@ export async function executeNaturalLanguageRoute(query, options) {
|
|
|
3927
4137
|
executionId: correlationId,
|
|
3928
4138
|
query,
|
|
3929
4139
|
simulationResult: simulationData,
|
|
3930
|
-
platformAnalysis: agentResults.map(p => ({ domain: p.domain, agent: p.agent, status: p.status, response: p.response })),
|
|
4140
|
+
platformAnalysis: agentResults.map(p => ({ domain: p.domain, agent: p.agent, status: p.status, response: p.response, execution_tier: p.execution_tier })),
|
|
3931
4141
|
correlationId,
|
|
3932
4142
|
timing: elapsed,
|
|
3933
4143
|
});
|
|
@@ -3951,25 +4161,18 @@ export async function executeNaturalLanguageRoute(query, options) {
|
|
|
3951
4161
|
// leaking state into subsequent runs (tests, daemons).
|
|
3952
4162
|
const degradations = drainDegradations(correlationId);
|
|
3953
4163
|
const phase1Verdict = computePhase1Verdict(invocations, degradations, artifactFilesGraphRouted);
|
|
3954
|
-
// ADR-PIPELINE-089 §2:
|
|
3955
|
-
//
|
|
3956
|
-
// keeping state in memory. Best-effort; manifest may not exist if
|
|
3957
|
-
// the artifact generator itself threw.
|
|
4164
|
+
// ADR-PIPELINE-089 §2 + ADR-PIPELINE-108 D5: persist verdict + dispatch
|
|
4165
|
+
// bounds (dedicated fleet-dispatch.json + best-effort manifest patch).
|
|
3958
4166
|
if (artifactRunDirGraphRouted) {
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
m['phase1_stats'] = phase1Verdict.stats;
|
|
3969
|
-
fsMod.writeFileSync(mPath, JSON.stringify(m, null, 2), 'utf-8');
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
catch { /* non-fatal */ }
|
|
4167
|
+
persistFleetDispatch(artifactRunDirGraphRouted, {
|
|
4168
|
+
...fleetCfg,
|
|
4169
|
+
dispatched: dispatchAgents.length,
|
|
4170
|
+
total: graphResult.agents.length,
|
|
4171
|
+
preflight_skipped: fleetPreflightSkipped,
|
|
4172
|
+
breaker_tripped: fleetBreaker.state().tripped,
|
|
4173
|
+
breaker_after_n_timeouts: fleetBreaker.state().after_n_timeouts,
|
|
4174
|
+
elapsed_ms: elapsed,
|
|
4175
|
+
}, phase1Verdict);
|
|
3973
4176
|
}
|
|
3974
4177
|
// One-line verdict banner — visible on the terminal and captured by
|
|
3975
4178
|
// the MCP tool wrapper for status surfacing.
|
|
@@ -4205,59 +4408,56 @@ export async function executeNaturalLanguageRoute(query, options) {
|
|
|
4205
4408
|
console.error(` [RUVECTOR] Simulation failed after ${MAX_RETRIES + 1} attempts: ${lastErr?.message ?? 'unknown error'}`);
|
|
4206
4409
|
return { kind: 'simulation', domain: 'simulator', agent: 'enterprise', status: 502, response: { error: lastErr?.message ?? 'Enterprise simulation failed after retries' } };
|
|
4207
4410
|
})();
|
|
4208
|
-
//
|
|
4209
|
-
//
|
|
4210
|
-
//
|
|
4211
|
-
//
|
|
4212
|
-
//
|
|
4213
|
-
const
|
|
4214
|
-
const
|
|
4215
|
-
//
|
|
4216
|
-
//
|
|
4411
|
+
// ADR-PIPELINE-108 D1/D2/D4/D5 — bounded, non-blocking fleet dispatch.
|
|
4412
|
+
// Per-agent timeout + cloud circuit breaker + preflight skip, so the
|
|
4413
|
+
// fleet cannot stall on the slowest of ~109 cloud calls. No per-agent
|
|
4414
|
+
// process.env toggling (it was racy under concurrency, and
|
|
4415
|
+
// executeAgentsInvokeCommand no longer branches on repo mode).
|
|
4416
|
+
const fleetCfg = resolveFleetDispatchConfig();
|
|
4417
|
+
const fleetBreaker = new CloudCircuitBreaker(fleetCfg.cloud_breaker_threshold);
|
|
4418
|
+
// D4: skip agents whose cloud backend is already unhealthy. Locally-served
|
|
4419
|
+
// agents (copilot/*, inference-gateway/route) are never skipped.
|
|
4420
|
+
const fleetSkip = await fleetPreflightSkipSet(fleetAgents, correlationId);
|
|
4421
|
+
const dispatchAgents = fleetAgents.filter((a) => !fleetSkip.has(`${a.domain}/${a.agent}`));
|
|
4422
|
+
const fleetPreflightSkipped = fleetAgents.length - dispatchAgents.length;
|
|
4423
|
+
// Dispatch in staggered waves to reduce GCF cold-start pressure.
|
|
4217
4424
|
const WAVE_SIZE = 15;
|
|
4218
4425
|
const WAVE_DELAY_MS = 2000;
|
|
4426
|
+
const completedFleet = [];
|
|
4219
4427
|
const agentPromises = [];
|
|
4220
|
-
for (let i = 0; i <
|
|
4221
|
-
const wave =
|
|
4428
|
+
for (let i = 0; i < dispatchAgents.length; i += WAVE_SIZE) {
|
|
4429
|
+
const wave = dispatchAgents.slice(i, i + WAVE_SIZE);
|
|
4222
4430
|
const waveNum = Math.floor(i / WAVE_SIZE) + 1;
|
|
4223
|
-
const totalWaves = Math.ceil(
|
|
4431
|
+
const totalWaves = Math.ceil(dispatchAgents.length / WAVE_SIZE);
|
|
4224
4432
|
console.error(` Wave ${waveNum}/${totalWaves}: ${wave.length} agents (${wave.map(a => `${a.domain}/${a.agent}`).slice(0, 3).join(', ')}${wave.length > 3 ? '...' : ''})`);
|
|
4225
4433
|
for (const { domain, agent } of wave) {
|
|
4226
|
-
agentPromises.push((
|
|
4227
|
-
|
|
4228
|
-
// Toggle repo-local per-agent: only copilot uses repo-local
|
|
4229
|
-
if (!REPO_LOCAL_DOMAINS.has(domain)) {
|
|
4230
|
-
process.env['AGENTICS_LOCAL_AGENTS'] = 'false';
|
|
4231
|
-
}
|
|
4232
|
-
else if (prevLocalAgents !== undefined) {
|
|
4233
|
-
process.env['AGENTICS_LOCAL_AGENTS'] = prevLocalAgents;
|
|
4234
|
-
}
|
|
4235
|
-
else {
|
|
4236
|
-
delete process.env['AGENTICS_LOCAL_AGENTS'];
|
|
4237
|
-
}
|
|
4238
|
-
const payload = buildDomainPayload(domain, agent, query, correlationId);
|
|
4239
|
-
const result = await executeAgentsInvokeCommand(domain, agent, payload, options);
|
|
4240
|
-
return { kind: 'agent', domain, agent, status: result.status ?? 200, response: result.response };
|
|
4241
|
-
}
|
|
4242
|
-
catch (err) {
|
|
4243
|
-
return { kind: 'agent', domain, agent, status: 502, response: { error: err instanceof Error ? err.message : String(err) } };
|
|
4244
|
-
}
|
|
4245
|
-
})());
|
|
4434
|
+
agentPromises.push(boundedFleetInvoke(domain, agent, query, options, correlationId, fleetBreaker, fleetCfg.agent_timeout_ms)
|
|
4435
|
+
.then((r) => { completedFleet.push(r); return r; }));
|
|
4246
4436
|
}
|
|
4247
4437
|
// Stagger between waves (skip delay on last wave)
|
|
4248
|
-
if (i + WAVE_SIZE <
|
|
4438
|
+
if (i + WAVE_SIZE < dispatchAgents.length) {
|
|
4249
4439
|
await new Promise(resolve => setTimeout(resolve, WAVE_DELAY_MS));
|
|
4250
4440
|
}
|
|
4251
4441
|
}
|
|
4252
|
-
|
|
4442
|
+
// D2: fleet-wide deadline — collect partial results rather than hang on
|
|
4443
|
+
// any still-pending agent. boundedFleetInvoke never rejects, so the
|
|
4444
|
+
// completedFleet snapshot is authoritative on timeout.
|
|
4445
|
+
const settledOrTimeout = await raceFleetDeadline(Promise.allSettled([Promise.resolve(simResult), ...agentPromises]), fleetCfg.fleet_timeout_ms);
|
|
4253
4446
|
const elapsed = Date.now() - start;
|
|
4254
|
-
|
|
4255
|
-
if (
|
|
4256
|
-
|
|
4447
|
+
let allResults;
|
|
4448
|
+
if (settledOrTimeout === 'TIMEOUT') {
|
|
4449
|
+
allResults = [
|
|
4450
|
+
{ status: 'fulfilled', value: simResult },
|
|
4451
|
+
...completedFleet.map((v) => ({ status: 'fulfilled', value: v })),
|
|
4452
|
+
];
|
|
4453
|
+
console.error(` [FLEET] deadline ${fleetCfg.fleet_timeout_ms}ms hit — ${completedFleet.length}/${dispatchAgents.length} agents completed`);
|
|
4257
4454
|
}
|
|
4258
4455
|
else {
|
|
4259
|
-
|
|
4456
|
+
allResults = settledOrTimeout;
|
|
4260
4457
|
}
|
|
4458
|
+
const fleetBreakerState = fleetBreaker.state();
|
|
4459
|
+
console.error(` [FLEET] dispatched ${dispatchAgents.length}/${fleetAgents.length} in ${(elapsed / 1000).toFixed(1)}s (preflight-skipped ${fleetPreflightSkipped}; breaker ${fleetBreakerState.tripped ? `TRIPPED after ${fleetBreakerState.after_n_timeouts}` : 'ok'}; bounds per-agent=${fleetCfg.agent_timeout_ms}ms fleet=${fleetCfg.fleet_timeout_ms}ms)`);
|
|
4460
|
+
console.error(` [TIER] execution: ${summarizeTiers(completedFleet)}`);
|
|
4261
4461
|
// Build multi-result response from ALL agents
|
|
4262
4462
|
const intents = [];
|
|
4263
4463
|
const invocations = [];
|
|
@@ -4310,7 +4510,7 @@ export async function executeNaturalLanguageRoute(query, options) {
|
|
|
4310
4510
|
executionId: correlationId,
|
|
4311
4511
|
query,
|
|
4312
4512
|
simulationResult: simulationData,
|
|
4313
|
-
platformAnalysis: agentResults.map(p => ({ domain: p.domain, agent: p.agent, status: p.status, response: p.response })),
|
|
4513
|
+
platformAnalysis: agentResults.map(p => ({ domain: p.domain, agent: p.agent, status: p.status, response: p.response, execution_tier: p.execution_tier })),
|
|
4314
4514
|
correlationId,
|
|
4315
4515
|
timing: elapsed,
|
|
4316
4516
|
});
|
|
@@ -4327,20 +4527,20 @@ export async function executeNaturalLanguageRoute(query, options) {
|
|
|
4327
4527
|
const fleetDegradations = drainDegradations(correlationId);
|
|
4328
4528
|
const fleetPhase1Verdict = computePhase1Verdict(invocations, fleetDegradations, artifactFilesFleet);
|
|
4329
4529
|
if (artifactRunDirFleet) {
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
}
|
|
4343
|
-
|
|
4530
|
+
// ADR-PIPELINE-108 D5: record the effective dispatch bounds so a run's
|
|
4531
|
+
// timing envelope is reproducible against the config in force. Written
|
|
4532
|
+
// BOTH as a dedicated artifact (fleet-dispatch.json — never rewritten by
|
|
4533
|
+
// later phases) and best-effort into manifest.json.
|
|
4534
|
+
const fleetDispatchRecord = {
|
|
4535
|
+
...fleetCfg,
|
|
4536
|
+
dispatched: dispatchAgents.length,
|
|
4537
|
+
total: fleetAgents.length,
|
|
4538
|
+
preflight_skipped: fleetPreflightSkipped,
|
|
4539
|
+
breaker_tripped: fleetBreakerState.tripped,
|
|
4540
|
+
breaker_after_n_timeouts: fleetBreakerState.after_n_timeouts,
|
|
4541
|
+
elapsed_ms: elapsed,
|
|
4542
|
+
};
|
|
4543
|
+
persistFleetDispatch(artifactRunDirFleet, fleetDispatchRecord, fleetPhase1Verdict);
|
|
4344
4544
|
}
|
|
4345
4545
|
if (fleetPhase1Verdict.verdict !== 'healthy') {
|
|
4346
4546
|
const label = fleetPhase1Verdict.verdict.toUpperCase();
|